> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Querying Logs

> Querying test data with Naite.get()

Learn how to query recorded logs using Naite.get() and chaining methods.

## Naite.get() Overview

<CardGroup cols={2}>
  <Card title="Wildcard Patterns" icon="asterisk">
    Flexible key matching

    Hierarchical queries
  </Card>

  <Card title="Chaining Queries" icon="link">
    fromFile, fromFunction

    where conditions
  </Card>

  <Card title="Various Results" icon="list">
    result, first, last

    at(index)
  </Card>

  <Card title="Callstack Usage" icon="layer-group">
    Call location tracking

    Debugging support
  </Card>
</CardGroup>

## Basic Usage

### Query by Key

```typescript theme={null}
import { Naite } from "sonamu";

// Record log
Naite.t("user:create", { userId: 123, username: "john" });

// Query by exact key
const data = Naite.get("user:create").result();
// [{ userId: 123, username: "john" }]

// First data
const first = Naite.get("user:create").first();
// { userId: 123, username: "john" }
```

### Wildcard Patterns

```typescript theme={null}
// Record multiple logs
Naite.t("user:create", { action: "create" });
Naite.t("user:update", { action: "update" });
Naite.t("user:delete", { action: "delete" });
Naite.t("post:create", { action: "create" });

// user:* pattern
const userLogs = Naite.get("user:*").result();
// Matches user:create, user:update, user:delete
// [{ action: "create" }, { action: "update" }, { action: "delete" }]

// *:create pattern
const createLogs = Naite.get("*:create").result();
// Matches user:create, post:create
```

## Wildcard Pattern Rules

### 1. Prefix Matching

When ending with `*`, prefix matching:

```typescript theme={null}
Naite.t("syncer:renderTemplate", {
  /* ... */
});
Naite.t("syncer:renderTemplate:user", {
  /* ... */
});
Naite.t("syncer:writeFile", {
  /* ... */
});

// "syncer:*" → All keys starting with syncer: regardless of length
const logs = Naite.get("syncer:*").result();
// syncer:renderTemplate
// syncer:renderTemplate:user
// syncer:writeFile
// All matched
```

### 2. Middle Wildcard

When `*` is in the middle:

```typescript theme={null}
Naite.t("syncer:renderTemplate:user", {
  /* ... */
});
Naite.t("syncer:writeFile:user", {
  /* ... */
});
Naite.t("syncer:renderTemplate:post", {
  /* ... */
});

// "syncer:*:user" → Middle part is * (same length)
const logs = Naite.get("syncer:*:user").result();
// syncer:renderTemplate:user
// syncer:writeFile:user
// Matched

// syncer:renderTemplate:post not matched (last is not user)
```

### 3. Exact Matching

Without wildcard, only exact key matches:

```typescript theme={null}
Naite.t("user:create", {
  /* ... */
});
Naite.t("user:create:validation", {
  /* ... */
});

// "user:create" → Exact match only
const logs = Naite.get("user:create").result();
// Only user:create matched
// user:create:validation not matched
```

## Chaining Methods

### fromFile() - Filter by Filename

Query only logs called from a specific file:

```typescript theme={null}
test("file filtering", async () => {
  // Logs recorded from multiple files
  // From user.model.test.ts:
  Naite.t("test:log", { file: "user.model.test.ts" });

  // From post.model.test.ts:
  Naite.t("test:log", { file: "post.model.test.ts" });

  // Query only logs from user.model.test.ts
  const userLogs = Naite.get("test:log").fromFile("user.model.test.ts").result();

  expect(userLogs).toHaveLength(1);
  expect(userLogs[0].file).toBe("user.model.test.ts");
});
```

**How it works**:

```typescript theme={null}
// Checks if callstack's filePath ends with /.../{fileName}
fromFile(fileName: string): NaiteQuery {
  const filtered = this.traces.filter((t) =>
    t.stack.some((frame) => frame.filePath.endsWith(`/${fileName}`))
  );
  return new NaiteQuery(filtered);
}
```

### fromFunction() - Filter by Function Name

Query only logs called from a specific function:

```typescript theme={null}
async function renderTemplate(template: string) {
  Naite.t("syncer:render", { template });
  // ...
}

async function writeFile(path: string) {
  Naite.t("syncer:write", { path });
  // ...
}

test("function filtering", async () => {
  await renderTemplate("user.model.ts");
  await writeFile("output.ts");

  // Only logs called from renderTemplate
  const renderLogs = Naite.get("syncer:*").fromFunction("renderTemplate").result();

  expect(renderLogs).toHaveLength(1);
  expect(renderLogs[0].template).toBe("user.model.ts");
});
```

**Options**:

```typescript theme={null}
// Direct calls only (stack[0])
fromFunction("renderTemplate", { from: "direct" });

// Indirect calls only (stack[1+])
fromFunction("renderTemplate", { from: "indirect" });

// Both (default)
fromFunction("renderTemplate", { from: "both" });
fromFunction("renderTemplate"); // Same
```

**Practical example**:

```typescript theme={null}
async function processUser(userId: number) {
  Naite.t("user:process:start", { userId });

  await createUser(userId);

  Naite.t("user:process:done", { userId });
}

async function createUser(userId: number) {
  Naite.t("user:create", { userId });
}

test("call relationship tracking", async () => {
  await processUser(123);

  // Only logs directly called from processUser
  const directLogs = Naite.get("user:*").fromFunction("processUser", { from: "direct" }).result();

  // user:process:start, user:process:done
  expect(directLogs).toHaveLength(2);

  // All logs in processUser's call chain
  const allLogs = Naite.get("user:*").fromFunction("processUser", { from: "both" }).result();

  // user:process:start, user:create, user:process:done
  expect(allLogs).toHaveLength(3);
});
```

### where() - Filter by Data Conditions

Filter by specific fields in log data:

```typescript theme={null}
test("condition filtering", async () => {
  Naite.t("user:create", { userId: 1, status: "active" });
  Naite.t("user:create", { userId: 2, status: "inactive" });
  Naite.t("user:create", { userId: 3, status: "active" });

  // userId > 1
  const logs = Naite.get("user:create").where("data.userId", ">", 1).result();

  expect(logs).toHaveLength(2);
  expect(logs[0].userId).toBe(2);
  expect(logs[1].userId).toBe(3);

  // Only where status is active
  const activeLogs = Naite.get("user:create").where("data.status", "=", "active").result();

  expect(activeLogs).toHaveLength(2);
});
```

**Supported operators**:

```typescript theme={null}
where("data.userId", ">", 10); // Greater than
where("data.userId", "<", 100); // Less than
where("data.userId", ">=", 10); // Greater than or equal
where("data.userId", "<=", 100); // Less than or equal
where("data.status", "=", "active"); // Equal
where("data.status", "!=", "deleted"); // Not equal
where("data.username", "includes", "john"); // Contains (string)
```

**Path specification**:

```typescript theme={null}
// Access data field
where("data.userId", "=", 123);

// Access nested objects (uses radashi's get)
where("data.user.profile.age", ">", 18);
```

## Chaining Combinations

Write complex queries by chaining multiple methods:

```typescript theme={null}
test("complex query", async () => {
  // Chain multiple conditions
  const logs = Naite.get("user:*")
    .fromFile("user.model.test.ts")
    .fromFunction("createUser")
    .where("data.userId", ">", 100)
    .where("data.status", "=", "active")
    .result();

  // From user.model.test.ts file
  // Called by createUser function
  // userId greater than 100
  // status is active
});
```

## Result Query Methods

### result() - Full Array

```typescript theme={null}
const logs = Naite.get("user:*").result();
// [{ ... }, { ... }, { ... }]
```

### first() - First

```typescript theme={null}
const first = Naite.get("user:create").first();
// { userId: 123, username: "john" }

// undefined if not found
const empty = Naite.get("nonexistent").first();
// undefined
```

### last() - Last

```typescript theme={null}
Naite.t("user:create", { userId: 1 });
Naite.t("user:create", { userId: 2 });
Naite.t("user:create", { userId: 3 });

const last = Naite.get("user:create").last();
// { userId: 3 }
```

### at(index) - nth

```typescript theme={null}
Naite.t("user:create", { userId: 1 });
Naite.t("user:create", { userId: 2 });
Naite.t("user:create", { userId: 3 });

const second = Naite.get("user:create").at(1);
// { userId: 2 }

// undefined if out of range
const outOfRange = Naite.get("user:create").at(10);
// undefined
```

## Practical Examples

### 1. Complex Flow Verification

```typescript theme={null}
test("post creation flow", async () => {
  const postModel = new PostModel();

  const { post } = await postModel.create({
    title: "Test Post",
    content: "Content",
    author_id: 1,
  });

  // Verify entire flow
  const allLogs = Naite.get("post:create:*").result();
  expect(allLogs.length).toBeGreaterThan(0);

  // Verify input data
  const input = Naite.get("post:create:input").first();
  expect(input.title).toBe("Test Post");

  // Verify output data
  const output = Naite.get("post:create:output").first();
  expect(output.postId).toBeGreaterThan(0);

  // Verify DB query
  const dbLogs = Naite.get("post:create:*").fromFunction("insertInto").result();
  expect(dbLogs.length).toBeGreaterThan(0);
});
```

### 2. Syncer Behavior Verification

```typescript theme={null}
test("Syncer template rendering", async () => {
  await Sonamu.syncer.generateTemplate("model", {
    entityId: "User",
  });

  // Verify renderTemplate function call
  const renderLogs = Naite.get("syncer:*").fromFunction("renderTemplate").result();

  expect(renderLogs.length).toBeGreaterThan(0);

  // Verify User entity processing
  const userLogs = Naite.get("syncer:*").where("data.entityId", "=", "User").result();

  expect(userLogs.length).toBeGreaterThan(0);
});
```

### 3. Error Occurrence Tracking

```typescript theme={null}
test("error situation tracking", async () => {
  try {
    await createUser({ username: "" }); // Invalid input
  } catch (error) {
    // Check error logs
    const errorLogs = Naite.get("user:create:error").result();
    expect(errorLogs.length).toBeGreaterThan(0);

    // Check error cause
    const validationErrors = Naite.get("user:*")
      .where("data.error", "includes", "validation")
      .result();

    expect(validationErrors.length).toBeGreaterThan(0);
  }
});
```

### 4. Performance Measurement

```typescript theme={null}
test("processing time measurement", async () => {
  Naite.t("process:start", { at: new Date() });

  // Long running task
  await longRunningTask();

  Naite.t("process:end", { at: new Date() });

  const logs = Naite.get("process:*").result();
  const start = new Date(logs[0].at);
  const end = new Date(logs[1].at);

  const duration = end.getTime() - start.getTime();
  expect(duration).toBeLessThan(1000); // Within 1 second
});
```

## Internal Structure

### NaiteQuery Class

```typescript theme={null}
export class NaiteQuery {
  constructor(private traces: NaiteTrace[]) {}

  fromFile(fileName: string): NaiteQuery {
    // Filter and return new NaiteQuery (chainable)
  }

  fromFunction(funcName: string, options?): NaiteQuery {
    // Filter and return new NaiteQuery
  }

  where(path: string, operator: string, value: any): NaiteQuery {
    // Filter and return new NaiteQuery
  }

  result(): any[] {
    // Return final data array
  }

  first(): any | undefined {
    // First data
  }

  last(): any | undefined {
    // Last data
  }

  at(index: number): any | undefined {
    // nth data
  }
}
```

### matchesPattern Logic

```typescript theme={null}
function matchesPattern(key: string, pattern: string): boolean {
  const keyParts = key.split(":");
  const patternParts = pattern.split(":");

  // Last is * → prefix matching (any length)
  if (patternParts[patternParts.length - 1] === "*") {
    const prefixParts = patternParts.slice(0, -1);
    return prefixParts.every((part, i) => part === keyParts[i]);
  }

  // Must be same length
  if (patternParts.length !== keyParts.length) {
    return false;
  }

  // Each part is * or exact match
  return patternParts.every((part, i) => part === "*" || part === keyParts[i]);
}
```

## Best Practices

### 1. Clear Key Structure

```typescript theme={null}
// ✅ Correct way: Hierarchical structure
Naite.t("module:function:action", {
  /* ... */
});

// Use wildcards when querying
Naite.get("module:*"); // Entire module
Naite.get("module:function:*"); // Specific function
```

### 2. Optimize Chaining Order

```typescript theme={null}
// ✅ Good way: Order by most filtering first
Naite.get("user:*")
  .fromFile("user.model.test.ts") // File filter (filters most)
  .fromFunction("createUser") // Function filter
  .where("data.userId", ">", 100) // Data filter
  .result();
```

### 3. Result Verification

```typescript theme={null}
// ✅ Correct way: Verify expected results
const logs = Naite.get("user:create").result();
expect(logs.length).toBeGreaterThan(0); // Check not empty

// Verify specific values
expect(logs[0].userId).toBeGreaterThan(0);
expect(logs[0].username).toBe("john");
```

## Cautions

<Warning>
  **Cautions when using Naite.get()**: 1. **Empty results**: Returns empty array if no logs 2.
  **Order**: Returns in recorded order 3. **Chaining**: Each method returns a new NaiteQuery 4.
  **Wildcards**: Understand pattern matching rules 5. **where path**: Uses radashi's get path format
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="Naite Viewer" icon="eye" href="/en/testing/naite/naite-viewer">
    VSCode Extension usage
  </Card>

  <Card title="Debugging Tests" icon="bug" href="/en/testing/naite/debugging-tests">
    Using callstack tracking
  </Card>

  <Card title="Recording Logs" icon="pen" href="/en/testing/naite/recording-logs">
    Detailed Naite.t() usage
  </Card>

  <Card title="What is Naite?" icon="circle-info" href="/en/testing/naite/what-is-naite">
    Naite overview
  </Card>
</CardGroup>
