# AI Agent Integration Source: https://docs.timberlogs.dev/ai-agents Make your AI coding agent aware of Timberlogs with one command. Timberlogs documentation is discoverable by 40+ AI coding agents via [skills.sh](https://skills.sh). Once installed, your agent knows how to set up Timberlogs, send logs, query data, and use the CLI. ## Install the skill Run this in your project directory: ```bash theme={null} npx skills add https://docs.timberlogs.dev ``` The CLI auto-detects your installed agents and lets you choose which ones to configure. ## Supported agents The Timberlogs skill works with any agent that supports the skills.sh standard, including: * **Claude Code** * **Cursor** * **GitHub Copilot** * **Windsurf** * **Gemini CLI** * **Codex** * **OpenCode** * **Amp** * And 30+ more ## What the skill provides Once installed, your AI agent can: * Set up Timberlogs in your project with the correct SDK and configuration * Send structured logs using the right API format * Query and search logs via the REST API or CLI * Track flows across multi-step operations * Follow best practices like flushing before exit and storing API keys in environment variables ## Custom skill The skill file is auto-generated from the documentation and updated on every deploy. You can view it directly at [docs.timberlogs.dev/skill.md](https://docs.timberlogs.dev/skill.md). # Authentication Source: https://docs.timberlogs.dev/api-reference/authentication API key authentication for the Timberlogs API. Timberlogs uses API Keys for authentication. ## API Keys API keys are the recommended method for server-side applications and SDKs. ### Key Format API keys follow the format: `tb_{environment}_{random}` * `tb_live_*` - Production keys * `tb_test_*` - Test/development keys ### Using API Keys Include your API key in the `Authorization` header: ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"logs": [...]}' ``` Or use the `X-API-Key` header: ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "X-API-Key: tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"logs": [...]}' ``` ### Creating API Keys 1. Sign in to [app.timberlogs.dev](https://app.timberlogs.dev) 2. Select your workspace 3. Navigate to **Settings > API Keys** 4. Click **Create API Key** 5. Store the key securely - it's only shown once ### Key Security * Keys are hashed before storage (we never store the raw key) * Use environment variables to store keys in your applications * Rotate keys periodically * Revoke compromised keys immediately ## Error Responses ### 401 Unauthorized ```json theme={null} { "error": "Missing API key" } ``` **How to resolve:** Check that the `Authorization` or `X-API-Key` header is included in your request. ```json theme={null} { "error": "Invalid API key" } ``` **How to resolve:** Verify the key format (`tb_live_*` / `tb_test_*`) and that it was copied correctly. If in doubt, generate a new key in **Settings > API Keys**. ```json theme={null} { "error": "API key has been revoked" } ``` **How to resolve:** This key has been revoked and can no longer be used. Create a new key in the dashboard under **Settings > API Keys**. ```json theme={null} { "error": "API key has expired" } ``` **How to resolve:** This key has expired. Create a new key in the dashboard under **Settings > API Keys**. ### 403 Forbidden ```json theme={null} { "error": "Access denied to workspace" } ``` **How to resolve:** The API key does not have access to the specified workspace. Verify the key belongs to the correct workspace. ```json theme={null} { "error": "User has no workspace" } ``` **How to resolve:** The account associated with this request has no workspace. Create or join a workspace in the dashboard. ### 404 Not Found ```json theme={null} { "error": "Workspace not found" } ``` **How to resolve:** The workspace ID doesn't exist. Double-check the `workspaceId` value in your request. ## Best Practices 1. **Store keys securely** - Never commit API keys to version control 2. **Use different keys per environment** - Separate test and production keys 3. **Rotate keys regularly** - Especially after team changes 4. **Revoke compromised keys immediately** - Generate a new key in the dashboard # Error Reference Source: https://docs.timberlogs.dev/api-reference/errors A consolidated reference of all error responses across the Timberlogs API. ## Error Responses | Status | Error Message | Endpoint | Resolution | | ------ | ------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | 400 | Validation error | [POST /v1/logs](/api-reference/ingestion) | Check the `issues` array for details. Common causes: invalid `level` value, missing required fields, or exceeding the 100-log batch limit. | | 400 | Search query must be at least 2 characters | [GET /v1/logs/search](/api-reference/search) | The `q` parameter must be at least 2 characters long. Provide a longer search query. | | 401 | Missing API key | All endpoints | Include the `Authorization` or `X-API-Key` header in your request. See [Authentication](/api-reference/authentication). | | 401 | Invalid API key | All endpoints | Verify the key format (`tb_live_*` / `tb_test_*`) and that it was copied correctly. Generate a new key if needed. | | 401 | API key has been revoked | All endpoints | This key has been revoked. Create a new key in **Settings > API Keys**. | | 401 | API key has expired | All endpoints | This key has expired. Create a new key in **Settings > API Keys**. | | 403 | Access denied to workspace | All endpoints | The API key does not have access to the specified workspace. Verify the key belongs to the correct workspace. | | 403 | User has no workspace | All endpoints | Create or join a workspace in the [dashboard](https://app.timberlogs.dev). | | 404 | Log not found | [GET /v1/logs/:id](/api-reference/query) | The log ID doesn't exist or belongs to a different workspace. Verify the ID and API key. | | 404 | Workspace not found | All endpoints | Double-check the `workspaceId` value in your request. | | 429 | Rate limit exceeded | [POST /v1/logs](/api-reference/ingestion) | Wait `retryAfter` seconds before retrying. Batch logs into fewer requests or upgrade your plan. | | 500 | Search failed | [GET /v1/logs/search](/api-reference/search) | Retry the request. If it persists, simplify the query or reduce search fields. | ## Rate Limits by Plan | Plan | Requests/minute | Logs/month | | ---------- | --------------- | ---------- | | Free | 60 | 10,000 | | Pro | 600 | 1,000,000 | | Enterprise | Unlimited | Custom | ## Error Response Format All errors follow a consistent JSON format: ```json theme={null} { "error": "Human-readable error message" } ``` Validation errors include additional detail: ```json theme={null} { "error": "Validation error", "issues": [ { "path": ["logs", 0, "level"], "message": "Invalid enum value" } ] } ``` Rate limit errors include a retry hint: ```json theme={null} { "error": "Rate limit exceeded", "retryAfter": 60 } ``` ## Endpoint Reference For full details on each endpoint's error handling: * [Authentication](/api-reference/authentication) - 401, 403, 404 errors * [Log Ingestion](/api-reference/ingestion) - 400, 401, 429 errors * [Query Logs](/api-reference/query) - 401, 404 errors * [Search](/api-reference/search) - 400, 401, 500 errors * [Statistics](/api-reference/stats) - 401 errors # Log Ingestion Source: https://docs.timberlogs.dev/api-reference/ingestion Send logs to Timberlogs via the ingestion API. Supports JSON, JSONL, syslog, plain text, CSV, and OBL formats. ## Endpoint **POST** `/v1/logs` **Base URL:** `https://timberlogs-ingest.enaboapps.workers.dev` ## Supported Formats The ingestion endpoint accepts logs in multiple formats. Format is resolved by priority: 1. **`?format=` query parameter** (highest priority) 2. **`Content-Type` header** 3. **Auto-detection** via content sniffing (fallback) | Format | Content-Type | `?format=` | Description | | ------ | ---------------------- | ---------- | -------------------------------------------------------- | | JSON | `application/json` | `json` | Default. `{ logs: [...] }`, bare array, or single object | | JSONL | `application/x-ndjson` | `jsonl` | One JSON object per line | | Syslog | `application/x-syslog` | `syslog` | RFC 5424 and RFC 3164 | | Text | `text/plain` | `text` | One log per line, auto-extracts timestamp and level | | CSV | `text/csv` | `csv` | Header row with field names, comma or tab delimited | | OBL | `application/x-obl` | `obl` | Open Board Logging format for AAC device logs | ## Request ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------------------------------------- | | `Authorization` | Yes | `Bearer ` | | `Content-Type` | No | Format hint (see table above). Defaults to `application/json` | ### Query Parameters | Parameter | Description | | ------------- | ------------------------------------------------------------------------- | | `format` | Explicit format override: `json`, `jsonl`, `syslog`, `text`, `csv`, `obl` | | `source` | Default `source` for logs that don't include one | | `environment` | Default `environment` for logs that don't include one | | `level` | Default `level` for logs that don't include one | | `dataset` | Default `dataset` for logs that don't include one | Default query parameters are useful for formats like plain text and syslog where `source` and `environment` may not be present in the log data. ### Body (JSON) ```json theme={null} { "logs": [ { "level": "info", "message": "User signed in", "source": "api-server", "environment": "production", "data": { "userId": "user_123" } } ] } ``` The JSON parser also accepts a bare array `[...]` or a single object `{...}`. ### Body (JSONL) ``` {"level":"error","message":"Connection refused","source":"api","environment":"production"} {"level":"info","message":"Request completed","source":"api","environment":"production"} ``` One JSON object per line. Empty lines are skipped. ### Body (Syslog) **RFC 5424:** ``` <165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused ``` **RFC 3164:** ``` <34>Jan 15 10:30:00 myhost api[1234]: Connection refused ``` Syslog severity is mapped to log levels: emergency/alert/critical/error → `error`, warning → `warn`, notice/info → `info`, debug → `debug`. The APP-NAME or TAG becomes `source`, and HOSTNAME/PROCID are stored in `data`. ### Body (Plain Text) ``` 2024-01-15 10:30:00 ERROR Connection refused to database 2024-01-15 10:30:01 INFO Retrying connection... ``` One log per line. The parser extracts timestamps (ISO 8601, `YYYY-MM-DD HH:MM:SS`, `MM/DD/YYYY HH:MM:SS`) and level keywords (`ERROR`, `WARN`, `WARNING`, `INFO`, `DEBUG`, case-insensitive). Use `?source=` and `?environment=` query params to set required fields. ### Body (CSV) ```csv theme={null} level,message,source,timestamp error,Connection refused,api,1705312200000 info,Request completed,api,1705312201000 ``` First row is the header. Column names must match log schema field names. Tab-delimited files are also accepted (auto-detected). Quoted fields with commas are supported. ### Body (OBL) ```json theme={null} { "format": "open-board-log-0.1", "user_id": "user_abc", "source": "my-aac-app", "sessions": [ { "id": "sess_1", "type": "log", "started": "2024-01-15T10:00:00Z", "ended": "2024-01-15T11:00:00Z", "events": [ { "id": "e1", "timestamp": "2024-01-15T10:05:00Z", "type": "button", "label": "hello", "spoken": true }, { "id": "e2", "timestamp": "2024-01-15T10:06:00Z", "type": "utterance", "text": "hello world" } ] } ] } ``` [Open Board Logging (.obl)](https://www.openboardformat.org/) is a standard format for AAC (Augmentative and Alternative Communication) device logs. Each event in each session becomes a separate log entry. Event types are `button`, `utterance`, `action`, and `note`. **Event mapping:** * **button** → message: `Button: {label}`, data includes `spoken`, `button_id`, `board_id` * **utterance** → message: `Utterance: {text}`, data includes constituent `buttons` * **action** → message: `Action: {action}`, data includes `destination_board_id`, `text` * **note** → message: `Note: {text}`, data includes `author_name` The OBL `user_id` maps to `userId`, `device_id` maps to `sessionId`, and session/event metadata is stored in `data`. The root `source` field is used as the log source. Use `?source=` and `?environment=` query params to set defaults. Files may start with a `/* ... */` comment block which is stripped before parsing. Auto-detection works by checking for `"open-board-log-"` in the JSON. ### Log Entry Schema | Field | Type | Required | Description | | ------------- | --------- | -------- | ---------------------------------------------- | | `level` | string | Yes | Log level: `debug`, `info`, `warn`, `error` | | `message` | string | Yes | Log message (1-10,000 characters) | | `source` | string | Yes | Application/service name (1-100 characters) | | `environment` | string | Yes | `development`, `staging`, or `production` | | `dataset` | string | No | Dataset name for grouping (default: `default`) | | `version` | string | No | Application version | | `userId` | string | No | User identifier | | `sessionId` | string | No | Session identifier | | `requestId` | string | No | Request identifier for correlation | | `data` | object | No | Arbitrary JSON data | | `errorName` | string | No | Error class/name | | `errorStack` | string | No | Error stack trace | | `tags` | string\[] | No | Array of tags (max 20 tags, 50 chars each) | | `timestamp` | number | No | Unix timestamp in milliseconds | | `flowId` | string | No | Flow identifier for grouping related logs | | `stepIndex` | number | No | Step index within a flow (0-1000) | Required fields can be provided via query parameter defaults when using non-JSON formats. ### Batch Limits * Minimum: 1 log per request * Maximum: 100 logs per request ## Response ### Success (200) ```json theme={null} { "success": true, "count": 5, "timestamp": 1704067200000 } ``` | Field | Type | Description | | ----------- | ------- | ----------------------------- | | `success` | boolean | Always `true` on success | | `count` | number | Number of logs ingested | | `timestamp` | number | Server timestamp of ingestion | ### Error Responses **400 Bad Request** - Invalid request body or parse error ```json theme={null} { "error": "Failed to parse line 3: invalid JSON", "code": "PARSE_ERROR" } ``` **How to resolve:** Check the error message for details. For JSONL, the line number is included. For JSON, check the `issues` array. Common causes: invalid `level` enum value, missing required fields, malformed input, or exceeding the 100-log batch limit. **401 Unauthorized** - Invalid or missing API key ```json theme={null} { "error": "Invalid API key" } ``` **How to resolve:** See [Authentication](/api-reference/authentication) for details on API key format and usage. **429 Too Many Requests** - Rate limit exceeded ```json theme={null} { "error": "Rate limit exceeded", "retryAfter": 60 } ``` **How to resolve:** Wait `retryAfter` seconds before retrying. Consider batching logs into fewer requests or upgrading your plan. ## Examples ### Basic Log (JSON) ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [{ "level": "info", "message": "Application started", "source": "my-app", "environment": "production" }] }' ``` ### JSONL ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/x-ndjson" \ -d '{"level":"error","message":"Connection refused","source":"api","environment":"production"} {"level":"info","message":"Request completed","source":"api","environment":"production"}' ``` ### Syslog ```bash theme={null} curl -X POST 'https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?format=syslog&environment=production' \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -d '<165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused <14>1 2024-01-15T10:30:01.000Z myhost api 1234 - - Retrying connection' ``` ### Plain Text ```bash theme={null} curl -X POST 'https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?source=nginx&environment=production' \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: text/plain" \ -d '2024-01-15 10:30:00 ERROR Connection refused to database 2024-01-15 10:30:01 INFO Retrying connection...' ``` ### CSV ```bash theme={null} curl -X POST 'https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?environment=production' \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: text/csv" \ -d 'level,message,source,timestamp error,Connection refused,api,1705312200000 info,Request completed,api,1705312201000' ``` ### OBL (AAC Device Logs) ```bash theme={null} curl -X POST 'https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?source=my-aac-app&environment=production' \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/x-obl" \ -d '{ "format": "open-board-log-0.1", "user_id": "user_abc", "sessions": [{ "id": "sess_1", "type": "log", "started": "2024-01-15T10:00:00Z", "ended": "2024-01-15T11:00:00Z", "events": [ {"id": "e1", "timestamp": "2024-01-15T10:05:00Z", "type": "button", "label": "I", "spoken": true}, {"id": "e2", "timestamp": "2024-01-15T10:05:01Z", "type": "button", "label": "want", "spoken": true}, {"id": "e3", "timestamp": "2024-01-15T10:05:02Z", "type": "utterance", "text": "I want juice"} ] }] }' ``` ### Log with Data ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [{ "level": "info", "message": "User signed in", "source": "auth-service", "environment": "production", "userId": "user_123", "sessionId": "sess_abc", "data": { "method": "oauth", "provider": "google" }, "tags": ["auth", "login"] }] }' ``` ### Error Log ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [{ "level": "error", "message": "Database connection failed", "source": "api-server", "environment": "production", "errorName": "ConnectionError", "errorStack": "ConnectionError: ECONNREFUSED\n at connect (/app/db.js:42:11)\n at ...", "tags": ["database", "critical"] }] }' ``` ### Batch Logs ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [ { "level": "info", "message": "Request received", "source": "api", "environment": "production", "requestId": "req_xyz" }, { "level": "debug", "message": "Processing request", "source": "api", "environment": "production", "requestId": "req_xyz" }, { "level": "info", "message": "Request completed", "source": "api", "environment": "production", "requestId": "req_xyz", "data": {"duration": 145} } ] }' ``` ### Flow Tracking ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [ { "level": "info", "message": "Checkout started", "source": "checkout-service", "environment": "production", "flowId": "checkout-a1b2c3d4", "stepIndex": 0 }, { "level": "info", "message": "Payment processed", "source": "checkout-service", "environment": "production", "flowId": "checkout-a1b2c3d4", "stepIndex": 1 } ] }' ``` ## Raw Ingestion via SDK The TypeScript and Python SDKs provide `ingestRaw()` / `ingest_raw()` methods that send pre-formatted data directly to this endpoint. The SDK handles Content-Type headers, query parameter encoding, and retry logic automatically. ```typescript TypeScript theme={null} await timber.ingestRaw(body, 'csv', { source: 'nginx', environment: 'production' }) ``` ```python Python theme={null} # Sync timber.ingest_raw(body, "csv", IngestRawOptions(source="nginx", environment="production")) # Async await timber.ingest_raw_async(body, "csv", IngestRawOptions(source="nginx", environment="production")) ``` This is equivalent to: ```bash theme={null} curl -X POST 'https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?format=csv&source=nginx&environment=production' \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" \ -H "Content-Type: text/csv" \ -d "$body" ``` See the [TypeScript SDK](/sdks/typescript#raw-format-ingestion) and [Python SDK](/sdks/python#raw-format-ingestion) docs for full details. ## Rate Limits Rate limits depend on your plan: | Plan | Requests/minute | Logs/month | | ---------- | --------------- | ---------- | | Free | 60 | 10,000 | | Pro | 600 | 1,000,000 | | Enterprise | Unlimited | Custom | When rate limited, you'll receive a `429` response with a `retryAfter` value indicating when to retry. ## Best Practices 1. **Batch logs** - Send multiple logs per request to reduce overhead 2. **Use the SDK** - The TypeScript and Python SDKs handle batching and retries automatically 3. **Include context** - Add `userId`, `sessionId`, and `requestId` for correlation 4. **Use structured data** - Put details in `data` rather than interpolating into `message` 5. **Tag appropriately** - Use consistent tags for filtering 6. **Handle errors** - Implement retry logic with exponential backoff 7. **Use explicit formats** - Prefer `?format=` or `Content-Type` over auto-detection for reliability 8. **Set defaults for non-JSON formats** - Use `?source=` and `?environment=` query params when sending plain text or syslog # Query Logs Source: https://docs.timberlogs.dev/api-reference/query Retrieve logs from Timberlogs with filtering and pagination. ## List Logs **GET** `/v1/logs` ### Query Parameters | Parameter | Type | Default | Description | | ------------- | ------ | ------- | ----------------------------------- | | `limit` | number | 100 | Number of logs to return (max 1000) | | `offset` | number | 0 | Number of logs to skip | | `level` | string | - | Filter by log level | | `source` | string | - | Filter by source | | `environment` | string | - | Filter by environment | | `dataset` | string | - | Filter by dataset | | `userId` | string | - | Filter by user ID | | `sessionId` | string | - | Filter by session ID | | `flowId` | string | - | Filter by flow ID | | `from` | number | - | Start timestamp (Unix ms) | | `to` | number | - | End timestamp (Unix ms) | ### Response ```json theme={null} { "logs": [ { "id": "log_abc123", "timestamp": 1704067200000, "level": "info", "message": "User signed in", "source": "api-server", "environment": "production", "userId": "user_123", "sessionId": "sess_abc", "data": {"method": "oauth"}, "tags": ["auth"] } ], "pagination": { "limit": 100, "offset": 0, "count": 50, "hasMore": false } } ``` ### Examples **Basic query:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by level:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?level=error" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by source and environment:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?source=api-server&environment=production" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by time range:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?from=1704067200000&to=1704153600000" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by user:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?userId=user_123" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by flow:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?flowId=checkout-a1b2c3d4" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Pagination:** ```bash theme={null} # First page curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?limit=50" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" # Second page curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?limit=50&offset=50" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ## Get Single Log **GET** `/v1/logs/:id` Retrieve a single log by its ID. ### Response ```json theme={null} { "log": { "id": "log_abc123", "timestamp": 1704067200000, "level": "info", "message": "User signed in", "source": "api-server", "environment": "production", "userId": "user_123", "sessionId": "sess_abc", "requestId": "req_xyz", "data": {"method": "oauth"}, "tags": ["auth"], "flowId": "login-x1y2z3", "stepIndex": 0 } } ``` ### Error Responses **401 Unauthorized** - Invalid or missing API key ```json theme={null} { "error": "Invalid API key" } ``` **How to resolve:** Ensure your `Authorization` or `X-API-Key` header is included and valid. See [Authentication](/api-reference/authentication) for details. **404 Not Found** - Log not found ```json theme={null} { "error": "Log not found" } ``` **How to resolve:** The log ID doesn't exist or belongs to a different workspace. Verify the ID and ensure your API key has access to the correct workspace. ### Example ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/log_abc123" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ## Log Object | Field | Type | Description | | ------------- | --------- | ------------------------------------------- | | `id` | string | Unique log identifier | | `timestamp` | number | Unix timestamp in milliseconds | | `level` | string | Log level: `debug`, `info`, `warn`, `error` | | `message` | string | Log message | | `source` | string | Application/service name | | `environment` | string | Environment name | | `dataset` | string | Dataset name | | `version` | string | Application version | | `userId` | string | User identifier | | `sessionId` | string | Session identifier | | `requestId` | string | Request identifier | | `data` | object | Additional structured data | | `errorName` | string | Error class/name | | `errorStack` | string | Error stack trace | | `tags` | string\[] | Array of tags | | `country` | string | Country code (auto-detected) | | `ipAddress` | string | Client IP address | | `flowId` | string | Flow identifier | | `stepIndex` | number | Step index within flow | ## Pagination The API uses offset-based pagination: * `limit` - Number of items per page (default: 100, max: 1000) * `offset` - Number of items to skip * `hasMore` - Boolean indicating if more results exist To iterate through all logs: ```typescript theme={null} let offset = 0 const limit = 100 let hasMore = true while (hasMore) { const response = await fetch( `https://timberlogs-ingest.enaboapps.workers.dev/v1/logs?limit=${limit}&offset=${offset}`, { headers: { Authorization: `Bearer ${apiKey}` } } ) const data = await response.json() // Process data.logs console.log(`Fetched ${data.logs.length} logs`) hasMore = data.pagination.hasMore offset += limit } ``` # Search Source: https://docs.timberlogs.dev/api-reference/search Full-text search across your logs with advanced query syntax. ## Endpoint **GET** `/v1/logs/search` ## Query Parameters | Parameter | Type | Default | Description | | ------------- | ------ | --------- | -------------------------------- | | `q` | string | Required | Search query (min 2 characters) | | `fields` | string | `message` | Comma-separated fields to search | | `limit` | number | 50 | Number of results (max 500) | | `offset` | number | 0 | Results to skip | | `level` | string | - | Filter by log level | | `source` | string | - | Filter by source | | `environment` | string | - | Filter by environment | | `dataset` | string | - | Filter by dataset | | `from` | number | - | Start timestamp (Unix ms) | | `to` | number | - | End timestamp (Unix ms) | ### Searchable Fields * `message` - Log message (default) * `errorName` - Error class/name * `errorStack` - Error stack trace * `data` - JSON data field ## Search Syntax ### Basic Search Simple word search (case-insensitive): ``` payment ``` ### Multiple Terms (AND) All terms must be present: ``` payment failed ``` ### Phrase Search Match exact phrase: ``` "payment failed" ``` ### OR Search Match any of the terms: ``` payment OR checkout ``` ### Exclude Terms Exclude logs containing a term: ``` payment -refund ``` ### Combined ``` "payment failed" error -test ``` ## Response ```json theme={null} { "logs": [ { "id": "log_abc123", "timestamp": 1704067200000, "level": "error", "message": "Payment failed: insufficient funds", "source": "payment-service", "environment": "production" } ], "query": { "raw": "payment failed", "parsed": { "required": ["payment", "failed"], "phrases": [], "excluded": [], "optional": [] }, "fields": ["message"] }, "pagination": { "limit": 50, "offset": 0, "count": 15, "hasMore": false } } ``` ## Examples ### Basic Search ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=error" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ### Search Specific Fields ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=timeout&fields=message,errorStack" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ### Phrase Search ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=%22database%20connection%22" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ### Exclude Terms ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=error%20-debug" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ### Combined Filters ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=payment&level=error&source=payment-service&environment=production" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ### Time Range ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/logs/search?q=timeout&from=1704067200000&to=1704153600000" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ## Error Responses **401 Unauthorized** - Invalid or missing API key ```json theme={null} { "error": "Invalid API key" } ``` **How to resolve:** Ensure your `Authorization` or `X-API-Key` header is included and valid. See [Authentication](/api-reference/authentication) for details. **400 Bad Request** - Invalid query ```json theme={null} { "error": "Search query must be at least 2 characters" } ``` **How to resolve:** The `q` parameter must be at least 2 characters long. Provide a longer search query. **500 Internal Server Error:** ```json theme={null} { "error": "Search failed" } ``` **How to resolve:** Retry the request. If the error persists, try simplifying the query or reducing the number of search fields. ## Search Tips 1. **Start specific** - Use specific terms to narrow results 2. **Use phrases** - Wrap multi-word searches in quotes for exact matches 3. **Exclude noise** - Use `-term` to filter out irrelevant results 4. **Search errors** - Add `errorStack` to fields when debugging exceptions 5. **Combine with filters** - Use `level=error` to focus on errors only 6. **Time scope** - Use `from`/`to` to search specific time periods ## Performance * Search defaults to the `message` field for best performance * Adding more fields (especially `data`) increases query time * Use filters (`level`, `source`, etc.) to reduce the search space * For large result sets, use pagination with reasonable limits # Statistics Source: https://docs.timberlogs.dev/api-reference/stats Get aggregated metrics and usage statistics for your logs. ## Get Statistics **GET** `/v1/stats` Retrieve aggregated log counts grouped by time or source. ### Query Parameters | Parameter | Type | Default | Description | | ------------- | ------ | ---------- | ------------------------------------ | | `from` | string | 7 days ago | Start date (YYYY-MM-DD) | | `to` | string | Today | End date (YYYY-MM-DD) | | `groupBy` | string | `day` | Grouping: `hour`, `day`, or `source` | | `source` | string | - | Filter by source | | `environment` | string | - | Filter by environment | | `dataset` | string | - | Filter by dataset | ### Response **Grouped by day (default):** ```json theme={null} { "stats": [ { "date": "2024-01-02", "debug": 1500, "info": 8500, "warn": 350, "error": 150, "total": 10500 }, { "date": "2024-01-01", "debug": 1200, "info": 7800, "warn": 280, "error": 120, "total": 9400 } ], "totals": { "debug": 2700, "info": 16300, "warn": 630, "error": 270, "total": 19900 }, "period": { "from": "2024-01-01", "to": "2024-01-02", "groupBy": "day" } } ``` **Grouped by hour:** ```json theme={null} { "stats": [ { "date": "2024-01-02", "hour": 14, "debug": 100, "info": 450, "warn": 20, "error": 5, "total": 575 } ], "totals": { "..." : "..." }, "period": { "groupBy": "hour" } } ``` **Grouped by source:** ```json theme={null} { "stats": [ { "source": "api-server", "debug": 1500, "info": 8000, "warn": 300, "error": 100, "total": 9900 }, { "source": "worker", "debug": 500, "info": 2000, "warn": 50, "error": 20, "total": 2570 } ], "totals": { "..." : "..." }, "period": { "groupBy": "source" } } ``` ### Examples **Last 7 days by day:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/stats" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Last 24 hours by hour:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/stats?from=2024-01-01&to=2024-01-02&groupBy=hour" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **By source:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/stats?groupBy=source" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` **Filter by environment:** ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/stats?environment=production" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ## Get Summary **GET** `/v1/stats/summary` Quick overview of today's activity compared to yesterday. ### Response ```json theme={null} { "today": { "debug": 500, "info": 3500, "warn": 150, "error": 50, "total": 4200 }, "comparison": { "yesterdayTotal": 3800, "changePercent": 11, "trend": "up" } } ``` | Field | Description | | ----------------------------- | -------------------------------- | | `today.total` | Total logs today | | `today.debug/info/warn/error` | Counts by level | | `comparison.yesterdayTotal` | Yesterday's total for comparison | | `comparison.changePercent` | Percentage change from yesterday | | `comparison.trend` | `up`, `down`, or `stable` | ### Example ```bash theme={null} curl -X GET "https://timberlogs-ingest.enaboapps.workers.dev/v1/stats/summary" \ -H "Authorization: Bearer tb_live_xxxxxxxxxxxxx" ``` ## Error Responses **401 Unauthorized** - Invalid or missing API key ```json theme={null} { "error": "Invalid API key" } ``` **How to resolve:** All stats endpoints require authentication. Ensure your `Authorization` or `X-API-Key` header is included and valid. See [Authentication](/api-reference/authentication) for details on API key format and usage. ## Examples ### Dashboard Metrics Display key metrics in your application dashboard: ```typescript theme={null} const response = await fetch( 'https://timberlogs-ingest.enaboapps.workers.dev/v1/stats/summary', { headers: { Authorization: `Bearer ${apiKey}` } } ) const { today, comparison } = await response.json() console.log(`Today: ${today.total} logs`) console.log(`Errors: ${today.error}`) console.log(`Trend: ${comparison.trend} (${comparison.changePercent}%)`) ``` ### Error Rate Monitoring Track error rates over time: ```typescript theme={null} const response = await fetch( 'https://timberlogs-ingest.enaboapps.workers.dev/v1/stats?groupBy=day', { headers: { Authorization: `Bearer ${apiKey}` } } ) const { stats } = await response.json() for (const day of stats) { const errorRate = ((day.error / day.total) * 100).toFixed(2) console.log(`${day.date}: ${errorRate}% errors`) } ``` ### Source Comparison Compare log volume across services: ```typescript theme={null} const response = await fetch( 'https://timberlogs-ingest.enaboapps.workers.dev/v1/stats?groupBy=source', { headers: { Authorization: `Bearer ${apiKey}` } } ) const { stats } = await response.json() for (const source of stats) { console.log(`${source.source}: ${source.total} logs`) } ``` # Flows Source: https://docs.timberlogs.dev/concepts/flows Group related logs together and track their sequence across multi-step operations. Flows group related logs together and track their sequence across a multi-step operation. Instead of searching through thousands of logs to piece together what happened during a checkout or a background job, a flow connects all the relevant logs with a shared identifier and ordered step indices. ## Why Flows? In any non-trivial application, a single user action triggers multiple log events across different functions, services, or even machines. Without flows, correlating these events means manually matching timestamps, user IDs, or request IDs — which breaks down quickly. Flows solve this by giving each operation a unique `flowId` and automatically numbering each step, so you get a complete, ordered timeline of what happened. ## Flow Anatomy Every log in a flow carries two extra fields: | Field | Description | | ------------- | -------------------------------------------------------------------------- | | **flowId** | Unique identifier linking all logs in the flow (e.g., `checkout-a1b2c3d4`) | | **stepIndex** | Auto-incrementing position in the sequence (0, 1, 2, ...) | The flow also has a **name** — the human-readable prefix of the flow ID (e.g., `checkout` from `checkout-a1b2c3d4`). ## Flow ID Generation Flow IDs follow the pattern `{name}-{random8chars}`: ``` checkout-a1b2c3d4 user-onboarding-x7y8z9ab api-request-mn0p1q2r ``` The suffix uses cryptographically secure random values to ensure uniqueness. ## Use Cases Flows are ideal for tracking: * **Checkout processes** — cart validation, payment, order creation, confirmation * **API request lifecycles** — receive, authenticate, process, respond * **Background jobs** — start, process items, complete or fail * **User journeys** — sign-up, onboarding steps, activation * **Data pipelines** — ingest, transform, validate, store ## Step Indexing Each log in a flow automatically receives an incrementing `stepIndex`, starting at 0. This preserves the order of operations regardless of when logs arrive at the server. ### Level Filtering Behavior When `minLevel` is configured, filtered logs do **not** increment the step index: ``` flow.debug('Not sent') // Filtered out, stepIndex not incremented flow.info('First log') // stepIndex: 0 flow.debug('Not sent') // Filtered out, stepIndex not incremented flow.info('Second log') // stepIndex: 1 ``` This ensures step indices remain sequential without gaps, even when some log levels are suppressed. ## Cross-Service Correlation Flows can span multiple services by passing the `flowId` between them. For example, an API server can start a flow and pass the flow ID to a background worker via a job queue. The worker then logs with the same `flowId`, continuing the sequence. This gives you a unified timeline across service boundaries. ## Best Practices ### Name Flows Descriptively Use names that clearly describe the operation: ``` checkout ✓ user-registration ✓ payment-processing ✓ flow1 ✗ process ✗ ``` ### Include Context in the First Log The first log in a flow should capture the key identifiers and parameters for the operation — user ID, order ID, item count, etc. This makes it easy to find and understand the flow later. ### Log Completion Always log the outcome of a flow, whether it succeeds or fails. This makes it clear whether a flow completed normally or was interrupted. ### Keep Flows Focused Each flow should represent a single logical operation. If an operation triggers sub-operations, consider creating separate flows for each rather than nesting everything in one. ## Further Reading * SDK Flow Tracking: [TypeScript](/sdks/typescript#flow-tracking) | [Python](/sdks/python#flow-tracking) — how to create and use flows in code * [Flow Timeline](/dashboard/flows) — viewing and analyzing flows in the dashboard # Log Levels Source: https://docs.timberlogs.dev/concepts/log-levels Understanding log level severity hierarchy and when to use each level in Timberlogs. Log levels indicate the severity of an event. Timberlogs supports four levels, ordered from least to most severe: ``` debug < info < warn < error ``` ## Severity Hierarchy | Level | Purpose | Example | | ------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `debug` | Detailed diagnostic information for development and troubleshooting | SQL queries, cache lookups, internal state | | `info` | Normal operational events and business milestones | User sign-ins, orders placed, jobs completed | | `warn` | Potential issues that don't block execution but need attention | Rate limits approaching, deprecated API usage, retry attempts | | `error` | Failures that require immediate investigation | Database connection failures, payment processing errors, unhandled exceptions | ## When to Use Each Level ### debug Use for information only needed during active debugging. These logs are typically high-volume and disabled in production: * Database query details and timing * Cache hit/miss events * Internal function entry/exit * Configuration values at startup ### info Use for events that confirm the system is working correctly. These form the baseline of operational visibility: * User actions (sign-in, sign-out, purchase) * Job or request completion * Service startup and shutdown * Scheduled task execution ### warn Use when something unexpected happens but the system can continue. Warnings often indicate future problems: * Rate limits nearing capacity * Fallback behavior triggered * Deprecated feature usage * Slow operations exceeding thresholds ### error Use for failures that need attention. Errors mean something went wrong and likely needs a fix: * Unhandled exceptions * External service failures * Data validation errors in critical paths * Resource exhaustion (disk, memory, connections) ## Level Filtering You can set a minimum log level to control which logs are sent. When `minLevel` is set, only logs at that level or higher are transmitted: | minLevel | Sends | | -------- | ------------------------ | | `debug` | debug, info, warn, error | | `info` | info, warn, error | | `warn` | warn, error | | `error` | error only | This is useful for reducing noise in production while keeping full verbosity in development. ## Dashboard Colors In the Timberlogs dashboard, each level is color-coded for quick visual scanning: | Level | Color | | ------- | ------ | | `debug` | Gray | | `info` | Blue | | `warn` | Yellow | | `error` | Red | ## Further Reading * [Structured Logging](/concepts/structured-logging) — how structured data makes logs searchable * SDK Logging Methods: [TypeScript](/sdks/typescript#logging-methods) | [Python](/sdks/python#logging-methods) — sending logs with different levels * [Viewing Logs](/dashboard/logs) — filtering and searching logs in the dashboard # Structured Logging Source: https://docs.timberlogs.dev/concepts/structured-logging Capture log data as key-value fields rather than plain text strings for searchable, filterable, machine-readable logs. Structured logging captures log data as key-value fields rather than plain text strings. Instead of embedding information in a message like `"User 123 placed order for $99.99"`, structured logging stores each piece of data separately — making logs searchable, filterable, and machine-readable. ## Why Structured Logging? Plain text logs are easy to write but hard to work with at scale: ``` [2024-01-15 10:30:45] INFO: User 123 placed order for $99.99 [2024-01-15 10:30:46] ERROR: Payment failed for user 456 on order ord_789 ``` Finding all orders over \$50, or all errors for a specific user, requires fragile text parsing. Structured logs solve this by storing data as discrete fields: ```json theme={null} { "level": "info", "message": "Order placed", "source": "api-server", "environment": "production", "data": { "userId": "user_123", "orderId": "ord_abc", "amount": 99.99 }, "tags": ["orders", "billing"] } ``` Now you can filter by `userId`, search by `level`, aggregate by `amount`, and alert on `tags` — without parsing text. ## Key Fields Every Timberlogs entry has a standard set of fields: | Field | Description | | --------------- | ---------------------------------------------------------- | | **message** | Human-readable description of the event | | **level** | Severity: `debug`, `info`, `warn`, or `error` | | **source** | Application or service name (e.g., `api-server`, `worker`) | | **environment** | Deployment target (e.g., `production`, `staging`) | | **data** | Arbitrary JSON object with event-specific details | | **tags** | Array of labels for categorization and filtering | | **timestamp** | When the event occurred | Additional optional fields include `userId`, `sessionId`, `requestId`, `flowId`, and `stepIndex` for correlation. ## Structured Data Enables Analysis With structured fields, you can: * **Filter** logs by level, source, environment, or tags * **Search** within the `data` object for specific values * **Correlate** related events using `requestId`, `sessionId`, or `flowId` * **Aggregate** numeric fields for performance analysis * **Alert** on specific tag or level combinations ## Datasets Datasets let you group and route logs by purpose. Set a `dataset` field on any log to organize logs into logical collections — for example, separating `billing` logs from `auth` logs. This is useful for access control, retention policies, and focused analysis. ## Further Reading * [Log Levels](/concepts/log-levels) — severity hierarchy and when to use each level * [Flows](/concepts/flows) — grouping related logs across multi-step operations * SDK Logging Methods: [TypeScript](/sdks/typescript#logging-methods) | [Python](/sdks/python#logging-methods) — how to send structured logs from your application # AI Log Assistant Source: https://docs.timberlogs.dev/dashboard/ai-assistant Ask questions about your logs in plain English and get answers backed by your real data. The AI Log Assistant lets you ask questions about your logs in plain English and get answers backed by your real data. Available on **Pro** and **Enterprise** plans. AI Log Assistant ## Getting Started Navigate to the **AI** page from the sidebar. You can type a question or click one of the suggested prompts, which are generated from your live data. ## What You Can Ask ### Error Investigation * "What errors happened today?" * "What's causing the 500 errors in my API?" * "Show me the most common errors this week" ### Trends and Patterns * "Are errors increasing?" * "Compare error rates by service" * "Show me activity trends this week" ### Log Search * "Find logs mentioning timeout in production" * "Show me the latest error logs from auth-service" ### Flow Tracing * "Trace the request with flow ID abc-123" * "What went wrong in this flow?" ### Infrastructure * "What services are producing the most logs?" * "What's my retention policy?" * "List my API keys" ## Available Tools The AI assistant has access to 17 tools that connect to your live log data: | Tool | Description | | ----------------------------- | -------------------------------------------------------------------- | | **search\_logs** | Search and filter logs by level, source, environment, and time range | | **search\_logs\_fulltext** | Full-text search with phrases, boolean operators, and exclusions | | **count\_logs** | Count logs matching specific filters | | **get\_log\_detail** | Drill into a specific log entry with full stack traces and metadata | | **get\_aggregated\_errors** | Top errors grouped by message with counts | | **get\_metrics** | Log volume broken down by level with daily comparison | | **get\_stats\_over\_time** | Log statistics grouped by hour or day for trend analysis | | **get\_stats\_by\_source** | Log statistics grouped by source/service | | **get\_flow\_logs** | Trace multi-step requests by flow ID | | **list\_sources** | List available log sources | | **list\_alert\_rules** | View configured alert rules | | **get\_alert\_history** | Recent alert trigger history | | **preview\_alert** | Check what an alert rule would match right now | | **get\_org\_info** | Organization details, plan, and usage limits | | **list\_api\_keys** | API keys with usage info | | **list\_members** | Team members and roles | | **list\_alert\_destinations** | Alert notification destinations | ## Smart Suggestions The assistant generates context-aware suggestions based on your live data: * If you have errors today, it suggests investigating them * If error rates are trending up, it highlights the change * Suggestions adapt to your specific sources and error patterns After each response, follow-up suggestions appear based on the tools used in the answer. ## Plan Requirements | Plan | Access | | ---------- | ---------------------------------------- | | Free | View-only (upsell prompt on interaction) | | Pro | Full access | | Enterprise | Full access | # Flow Timeline Source: https://docs.timberlogs.dev/dashboard/flows View related logs grouped together to trace multi-step operations. The Flow Timeline view shows related logs grouped together, making it easy to trace multi-step operations. Flow Timeline ## What is a Flow? A flow is a series of related logs connected by a shared `flowId` and ordered by `stepIndex`. See [Flows](/concepts/flows) for a conceptual overview. ## Viewing Flows ### From the Logs View 1. Click any log that has a flow ID 2. In the detail panel, click the flow ID link 3. View all logs in that flow ### Flow Timeline View The flow timeline displays: * All logs in the flow, ordered by step index * Time elapsed between steps * Total flow duration * Visual timeline showing progress ## Flow Details ### Header * **Flow Name** - Extracted from flow ID (e.g., `checkout` from `checkout-a1b2c3d4`) * **Flow ID** - Full identifier * **Total Steps** - Number of logs in the flow * **Duration** - Time from first to last log * **Status** - Success/Error based on final log level ### Timeline Each step shows: * **Step Index** - Position in sequence (0, 1, 2...) * **Time** - When the step occurred * **Delta** - Time since previous step * **Level** - Log level (color-coded) * **Message** - Log message ### Example Flow ``` checkout-a1b2c3d4 Duration: 2.3s | 4 steps [0] 0ms info "Checkout started" [1] 150ms info "Cart validated" (+150ms) [2] 1.2s info "Payment processed" (+1.05s) [3] 2.3s info "Order confirmed" (+1.1s) ``` ## Filtering Flows ### By Flow ID Search for a specific flow: ``` flowId:checkout-a1b2c3d4 ``` ### By Flow Name Search flows by prefix: ``` flowId:checkout-* ``` ### By Status Filter to failed flows (those ending with an error): 1. Filter logs to `error` level 2. Look for logs with `flowId` 3. Click through to see the full flow ## Flow Analysis ### Identifying Bottlenecks Look at the time delta between steps: ``` [0] 0ms "Started" [1] 50ms "Validated" (+50ms) ← Fast [2] 3.5s "Payment done" (+3.45s) ← Slow step! [3] 3.6s "Complete" (+100ms) ← Fast ``` The payment step took 3.45 seconds - a potential optimization target. ### Error Analysis When a flow ends in error: ``` [0] info "Checkout started" [1] info "Cart validated" [2] error "Payment failed: Card declined" ``` The flow stops at step 2 with an error - expand to see the full error details and stack trace. ### Missing Steps If step indices have gaps, the flow may be incomplete: ``` [0] info "Started" [2] info "Processing" ← Step 1 missing! [3] info "Complete" ``` Check your log level settings - filtered logs won't increment the step index, but SDK bugs might. ## Best Practices See [Flows — Best Practices](/concepts/flows#best-practices) for naming, context, completion logging, and cross-service correlation guidelines. ## SDK Support Create flows using the SDK: ```typescript theme={null} import { createTimberlogs } from 'timberlogs-client' const timber = createTimberlogs({ source: 'my-app', environment: 'production', apiKey: process.env.TIMBER_API_KEY, }) const flow = timber.flow('checkout') flow.info('Started checkout') flow.info('Cart validated') flow.info('Payment processed') flow.info('Order confirmed') ``` See Flow Tracking in the SDK: [TypeScript](/sdks/typescript#flow-tracking) | [Python](/sdks/python#flow-tracking). # Viewing Logs Source: https://docs.timberlogs.dev/dashboard/logs The Logs view is the primary interface for exploring your application logs in real-time. The Logs view is the primary interface for exploring your application logs in real-time. Log List View ## Log List The main logs view displays logs in reverse chronological order (newest first). ### Log Row Each log row shows: * **Timestamp** - When the log was created * **Level** - Color-coded badge (debug, info, warn, error) * **Source** - Application or service name * **Message** - Log message (truncated) ### Level Colors Each log level is color-coded for quick scanning. See [Log Levels](/concepts/log-levels#dashboard-colors) for the full color reference. ## Filtering ### Level Filter Click the level dropdown to filter by log level: * **All** - Show all levels * **debug** - Debug logs only * **info** - Info and above * **warn** - Warnings and errors * **error** - Errors only ### Source Filter Type or select a source to filter logs from a specific service: ``` api-server worker frontend ``` ### Environment Filter Filter by deployment environment: * development * staging * production ### Time Range Set a custom date range using the **from** and **to** datetime pickers. ## Search Use the search bar to find logs by content. ### Basic Search Type keywords to find matching logs: ``` payment error ``` ### Phrase Search Use quotes for exact phrases: ``` "database connection failed" ``` ### Exclude Terms Prefix with `-` to exclude: ``` error -debug ``` ### OR Queries Use `OR` between terms: ``` timeout OR refused ``` ## Log Detail View Click any log row to expand it and see full details. ### Fields Displayed * **Full message** - Complete log message * **Timestamp** - Precise time with milliseconds * **Source** - Application name * **Environment** - Where the log originated * **Level** - Log severity * **User ID** - Associated user (if set) * **Session ID** - Session identifier * **Request ID** - Request correlation ID * **Dataset** - Log routing dataset * **Version** - Application version * **IP Address** - Client IP address * **Country** - Resolved country ### Data Section If the log includes structured data, it's displayed as formatted JSON: ```json theme={null} { "userId": "user_123", "action": "checkout", "amount": 99.99, "currency": "USD" } ``` ### Error Details For error logs: * **Error Name** - The exception type * **Stack Trace** - Full stack trace with line numbers ### Tags Tags are displayed as badges: ``` [billing] [critical] [pagerduty] ``` ### Flow Information If the log is part of a flow: * **Flow ID** - Links to flow timeline view * **Step Index** - Position in the flow sequence ## Loading More Logs are loaded in batches. Click **Load More** at the bottom to fetch older logs. ## Copying Log Data ### Copy Log ID Click the copy icon next to a log to copy its ID. ### Copy JSON In the detail view, click "Copy JSON" to copy the full log as JSON. ## Tips ### Debugging Issues 1. Filter to `error` level 2. Set appropriate time range 3. Search for relevant keywords 4. Expand logs to see stack traces 5. Use request/session IDs for correlation ### Monitoring 1. Keep the view on `error` or `warn` 2. Set source filter to critical services 3. Use flows for multi-step operations ### Performance Analysis 1. Search for "slow" or "timeout" 2. Look at `data` field for timing info 3. Group by source to find problematic services # Dashboard Overview Source: https://docs.timberlogs.dev/dashboard/overview The Timberlogs dashboard provides a real-time view of your application logs. The Timberlogs dashboard at [app.timberlogs.dev](https://app.timberlogs.dev) provides a real-time view of your application logs. Dashboard Overview ## Getting Started 1. Sign in at [app.timberlogs.dev](https://app.timberlogs.dev) 2. Create or select a workspace 3. Generate an API key in **Settings > API Keys** 4. Start sending logs using the [SDK](/sdks/typescript) or [API](/api-reference/ingestion) ## Dashboard Layout ### Navigation * **Dashboard** - Today's activity, recent logs, and quick actions * **AI** - Ask questions about your logs in plain English * **Logs** - View and search all logs * **Settings** - Manage API keys, alerts, sources, integrations, team, and billing ### Workspace Selector Switch between workspaces using the dropdown in the header. Each workspace has its own: * Logs and data * API keys * Team members * Billing plan ## Key Features ### Real-time Log Viewing * Logs appear within seconds of ingestion * Click any log to expand and see full details ### Filtering Filter logs by: * **Level** - debug, info, warn, error * **Source** - Application or service name * **Environment** - development, staging, production * **Time range** - Custom date range with from/to pickers ### Search Full-text search across log messages: * Simple keyword search * Phrase matching with quotes * Exclude terms with `-` * OR queries for alternatives See [Search documentation](/api-reference/search) for full syntax. ### Log Detail View Click any log row to expand and see: * Full message text * Structured data (JSON) * Error stack traces * All metadata fields * Tags ### Flow Tracking View related logs grouped by flow: * See all steps in a multi-step process * Track timing between steps * Debug user journeys end-to-end ## Data Retention Log retention depends on your plan: | Plan | Retention | | ---------- | --------- | | Free | 7 days | | Pro | 30 days | | Enterprise | Custom | Logs are automatically deleted after the retention period. ## Next Steps * [AI Log Assistant](/dashboard/ai-assistant) * [View and filter logs](/dashboard/logs) * [Track flows](/dashboard/flows) * [Configure the SDK](/sdks/configuration) # Getting Started Source: https://docs.timberlogs.dev/getting-started Get up and running with Timberlogs in under 5 minutes. ## Installation Install the Timberlogs SDK using your preferred package manager. ### TypeScript / JavaScript ```bash npm theme={null} npm install timberlogs-client ``` ```bash pnpm theme={null} pnpm add timberlogs-client ``` ```bash yarn theme={null} yarn add timberlogs-client ``` **Requirements:** Node.js 16 or later. TypeScript 4.7+ optional but recommended. ### Python ```bash pip theme={null} pip install timberlogs-client ``` ```bash uv theme={null} uv add timberlogs-client ``` ```bash poetry theme={null} poetry add timberlogs-client ``` **Requirements:** Python 3.8 or later. ### Rust ```toml Cargo.toml theme={null} [dependencies] timberlogs = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } ``` **Requirements:** Rust 2021 edition or later. Tokio async runtime. Sign up at [app.timberlogs.dev](https://app.timberlogs.dev) and create a workspace. Navigate to **Settings > API Keys** and create a new key. Your API key will look like: `tb_live_xxxxxxxxxxxxx` ```typescript TypeScript theme={null} import { createTimberlogs } from 'timberlogs-client' const timber = createTimberlogs({ source: 'my-app', // Your app/service name environment: 'production', // development, staging, or production apiKey: process.env.TIMBER_API_KEY, }) ``` ```python Python theme={null} import os from timberlogs import create_timberlogs timber = create_timberlogs( source="my-app", environment="production", api_key=os.getenv("TIMBER_API_KEY"), ) ``` ```rust Rust theme={null} use timberlogs::TimberlogsClient; use std::env; let timber = TimberlogsClient::new(timberlogs::TimberlogsConfig { source: "my-app".into(), environment: timberlogs::Environment::Production, api_key: env::var("TIMBER_API_KEY").unwrap(), ..Default::default() }); ``` ```typescript TypeScript theme={null} // Simple info log timber.info('Application started') // Log with data timber.info('User signed in', { userId: 'user_123', email: 'user@example.com' }) // Log a warning timber.warn('Rate limit approaching', { current: 950, limit: 1000 }) // Log an error with stack trace try { await riskyOperation() } catch (error) { timber.error('Operation failed', error) } ``` ```python Python theme={null} # Simple info log timber.info("Application started") # Log with data timber.info("User signed in", { "user_id": "user_123", "email": "user@example.com" }) # Log a warning timber.warn("Rate limit approaching", { "current": 950, "limit": 1000 }) # Log an error with stack trace try: risky_operation() except Exception as e: timber.error("Operation failed", e) ``` ```rust Rust theme={null} use serde_json::json; // Simple info log timber.info("Application started", None); // Log with data timber.info("User signed in", Some(json!({ "user_id": "user_123", "email": "user@example.com" }))); // Log a warning timber.warn("Rate limit approaching", Some(json!({ "current": 950, "limit": 1000 }))); // Log an error timber.error("Operation failed", Some(json!({ "error": "connection timeout" }))); ``` Head to [app.timberlogs.dev](https://app.timberlogs.dev) and select your workspace. Your logs will appear in the Logs view within seconds. The SDK batches logs for efficiency. Make sure to flush before your process exits: ```typescript TypeScript theme={null} // Before shutdown await timber.flush() // Or use disconnect which flushes automatically await timber.disconnect() ``` ```python Python theme={null} # Before shutdown timber.flush() # Or use disconnect which flushes automatically timber.disconnect() # Or use a context manager for automatic cleanup with create_timberlogs(...) as timber: timber.info("Auto-flushed on exit") ``` ```rust Rust theme={null} // Before shutdown timber.flush().await; // Or use disconnect which flushes automatically timber.disconnect().await; ``` ## Example: Express.js ```typescript theme={null} import express from 'express' import { createTimberlogs } from 'timberlogs-client' const app = express() const timber = createTimberlogs({ source: 'api-server', environment: process.env.NODE_ENV || 'development', apiKey: process.env.TIMBER_API_KEY, }) app.use((req, res, next) => { timber.info('Request received', { method: req.method, path: req.path, ip: req.ip, }) next() }) app.get('/health', (req, res) => { res.json({ status: 'ok' }) }) // Graceful shutdown process.on('SIGTERM', async () => { await timber.flush() process.exit(0) }) app.listen(3000) ``` ## Example: FastAPI ```python theme={null} import os from contextlib import asynccontextmanager from fastapi import FastAPI, Request from timberlogs import create_timberlogs timber = create_timberlogs( source="api-server", environment=os.getenv("ENVIRONMENT", "development"), api_key=os.getenv("TIMBER_API_KEY"), ) @asynccontextmanager async def lifespan(app: FastAPI): yield timber.disconnect() app = FastAPI(lifespan=lifespan) @app.middleware("http") async def log_requests(request: Request, call_next): timber.info("Request received", { "method": request.method, "path": request.url.path, "ip": request.client.host, }) return await call_next(request) @app.get("/health") def health(): return {"status": "ok"} ``` ## Example: Actix Web ```rust theme={null} use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse, middleware}; use timberlogs::{TimberlogsClient, TimberlogsConfig, Environment}; use std::env; #[actix_web::main] async fn main() -> std::io::Result<()> { let timber = TimberlogsClient::new(TimberlogsConfig { source: "api-server".into(), environment: Environment::Production, api_key: env::var("TIMBER_API_KEY").unwrap(), ..Default::default() }); let timber = web::Data::new(timber); HttpServer::new(move || { App::new() .app_data(timber.clone()) .route("/health", web::get().to(health)) }) .bind("0.0.0.0:3000")? .run() .await } async fn health() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({"status": "ok"})) } ``` ## Query Logs from the Terminal Install the [Timberlogs CLI](/cli/overview) to search and filter logs without leaving your terminal: ```bash theme={null} npm install -g timberlogs-cli timberlogs login timberlogs logs --level error --from 1h ``` ## Next Steps * Learn about all [configuration options](/sdks/configuration) * Explore the SDK reference: [TypeScript](/sdks/typescript) | [Python](/sdks/python) | [Rust](/sdks/rust) * Query logs from the terminal with the [CLI](/cli/overview) * Track user flows with [Flow Tracking](/concepts/flows) Ready to start logging? [Sign up free](https://app.timberlogs.dev) — send your first log in under 5 minutes. # Overview Source: https://docs.timberlogs.dev/introduction Structured logging made simple. Send logs from any application, search with ease, and track user flows. **Structured logging made simple.** Timberlogs is a modern logging service built for developers who want powerful log management without the complexity. Send logs from any application using our REST API or official SDKs, search and filter with ease, and track user flows across your system. Timberlogs Dashboard ## Features * **[REST API](/api-reference/ingestion)** - Send logs from any language or platform via simple HTTP POST * **[TypeScript SDK](/sdks/typescript)** - Drop-in SDK with automatic batching and retry * **[Python SDK](/sdks/python)** - Full-featured SDK with sync and async support * **[CLI](/cli/overview)** - Query logs, inspect flows, and view stats from your terminal * **[Powerful Search](/api-reference/search)** - Full-text search with support for phrases, exclusions, and OR queries * **[Flow Tracking](/concepts/flows)** - Link related logs together to trace user journeys * **[Real-time Dashboard](/dashboard/overview)** - Beautiful UI to view, search, and filter logs * **Cloudflare-powered** - Built on Cloudflare Workers for global low-latency ingestion ## Quick Example ### Using the REST API Send logs from any language with a simple HTTP request: ```bash theme={null} curl -X POST https://timberlogs-ingest.enaboapps.workers.dev/v1/logs \ -H "Authorization: Bearer tb_live_xxxxx" \ -H "Content-Type: application/json" \ -d '{ "logs": [{ "level": "info", "message": "User signed in", "source": "my-app", "environment": "production", "data": { "userId": "user_123" } }] }' ``` ### Using the TypeScript SDK ```typescript theme={null} import { createTimberlogs } from 'timberlogs-client' const timber = createTimberlogs({ source: 'my-app', environment: 'production', apiKey: 'tb_live_xxxxx', }) timber.info('User signed in', { userId: 'user_123' }) timber.error('Payment failed', new Error('Card declined')) ``` ### Using the Python SDK ```python theme={null} from timberlogs import create_timberlogs timber = create_timberlogs( source="my-app", environment="production", api_key="tb_live_xxxxx", ) timber.info("User signed in", {"userId": "user_123"}) timber.error("Payment failed", Exception("Card declined")) ``` ### Using the CLI ```bash theme={null} npm install -g timberlogs-cli timberlogs login timberlogs logs --level error --from 1h ``` ## Get Started Ready to add structured logging to your app? 1. [Getting Started guide](/getting-started) - Get up and running in 5 minutes 2. [TypeScript SDK](/sdks/typescript) - For Node.js/TypeScript projects 3. [Python SDK](/sdks/python) - For Python projects 4. [CLI](/cli/overview) - Query and search logs from your terminal 5. [REST API Reference](/api-reference/ingestion) - Send logs from any language Ready to start logging? [Sign up free](https://app.timberlogs.dev) — send your first log in under 5 minutes. # CLI Source: https://docs.timberlogs.dev/timberlogs-cli/cli/overview Query logs, inspect flows, and view stats from your terminal. ## Installation ```bash theme={null} npm install -g timberlogs-cli ``` **Requirements:** Node.js 20 or later. ## REPL Running `timberlogs` with no arguments launches an interactive REPL session — a persistent prompt where you can run any command without reinvoking the binary. ```bash theme={null} timberlogs ``` ``` ▲ Timberlogs v1.0.0 ● Acme Corp logs stats flows flows show whoami login logout config list help exit ──────────────────────────────────────────────────────────── ❯ logs --level error --from 24h ❯ stats --group-by source ❯ flows show checkout-abc-123 ``` Commands work exactly like the CLI — same flags, same output. Commands that produce a table (`logs`, `stats`, `flows show`) take over the full screen with keyboard navigation; press `q` to return to the prompt. **Builtins:** `help`, `clear`, `exit` **History:** Use `↑` / `↓` to navigate previous commands. History is saved to `~/.config/timberlogs/history.json`. ## Authentication The CLI uses OAuth device flow — no API keys needed. ```bash theme={null} timberlogs login ``` This opens your browser to sign in via Timberlogs. Once approved, the CLI stores a session token locally at `~/.config/timberlogs/config.json`. ```bash theme={null} timberlogs whoami # Check auth status timberlogs logout # Remove stored session ``` ## Querying Logs ```bash theme={null} timberlogs logs # Last hour of logs timberlogs logs --level error --from 24h # Errors from last 24 hours timberlogs logs --search "payment" # Full-text search timberlogs logs --source api-server # Filter by source timberlogs logs --flow-id checkout-abc # Logs for a specific flow ``` ### Filters | Flag | Description | | -------------- | -------------------------------------------------- | | `--level` | `debug`, `info`, `warn`, or `error` | | `--source` | Filter by source name | | `--env` | Filter by environment | | `--search` | Full-text search | | `--from` | Start time (`30m`, `1h`, `24h`, `7d`, or ISO 8601) | | `--to` | End time | | `--limit` | Max results (default: 50) | | `--user-id` | Filter by user ID | | `--session-id` | Filter by session ID | | `--flow-id` | Filter by flow ID | | `--dataset` | Filter by dataset | ### Interactive Mode The default log view is an interactive table. Use arrow keys to navigate, Enter to expand a log entry, and `q` to quit. ### Output Formats ```bash theme={null} timberlogs logs --format json # JSON array timberlogs logs --format jsonl # One JSON object per line timberlogs logs --format csv # CSV timberlogs logs --format text # Plain text timberlogs logs --format syslog # Syslog format ``` ## Flows ```bash theme={null} timberlogs flows # List recent flows timberlogs flows --from 7d # Flows from last 7 days timberlogs flows show # View flow timeline ``` ## Stats ```bash theme={null} timberlogs stats # Last 24 hours timberlogs stats --from 7d # Last 7 days timberlogs stats --group-by source # Group by source ``` | Flag | Description | | ------------ | ------------------------------------------- | | `--from` | Start time (default: `24h`) | | `--to` | End time | | `--group-by` | `hour`, `day`, or `source` (default: `day`) | | `--source` | Filter by source | | `--env` | Filter by environment | | `--dataset` | Filter by dataset | ## JSON Mode All commands support `--json` for machine-readable output. JSON mode is also auto-detected when stdout is piped. ```bash theme={null} # Explicit timberlogs logs --level error --json # Auto-detected (piped) timberlogs logs --level error | jq '.logs[].message' ``` ## Config ```bash theme={null} timberlogs config list # Show current config timberlogs config reset # Delete config file (prompts for confirmation) timberlogs config reset --force # Delete without confirmation ``` When running inside the REPL, `config reset` requires `--force` — interactive confirmation prompts are not supported in the REPL session. Session data is stored at `~/.config/timberlogs/config.json` with `600` permissions. Override the directory with the `TIMBERLOGS_CONFIG_DIR` environment variable. # Python SDK Source: https://docs.timberlogs.dev/timberlogs-python-sdk/sdks/python The official Timberlogs SDK for Python applications. ## Installation ```bash pip theme={null} pip install timberlogs-client ``` ```bash uv theme={null} uv add timberlogs-client ``` ```bash poetry theme={null} poetry add timberlogs-client ``` ## Basic Usage ```python theme={null} from timberlogs import create_timberlogs timber = create_timberlogs( source="my-app", environment="production", api_key="tb_live_xxxxxxxxxxxxx", ) timber.info("Hello, Timberlogs!") ``` ## Exports The SDK exports the following: ```python theme={null} from timberlogs import ( create_timberlogs, # Factory function TimberlogsClient, # Client class Flow, # Flow class for tracking LogEntry, # Log entry dataclass LogOptions, # Options for logging methods TimberlogsConfig, # Configuration dataclass FormatName, # Literal type for ingest formats IngestRawOptions, # Options for ingest_raw() ) ``` ## Logging Methods ### `debug(message, data?, options?)` Log debug-level messages for detailed diagnostic information. ```python theme={null} # Simple debug timber.debug("Cache lookup") # With data timber.debug("Cache hit", {"key": "user:123", "ttl": 3600}) # With tags from timberlogs import LogOptions timber.debug("Cache miss", {"key": "user:456"}, LogOptions(tags=["cache", "performance"])) ``` ### `info(message, data?, options?)` Log informational messages about normal operations. ```python theme={null} timber.info("User signed in", {"user_id": "user_123", "method": "oauth"}) timber.info("Order placed", {"order_id": "ord_xyz", "total": 99.99}) ``` ### `warn(message, data?, options?)` Log warning conditions that might indicate a problem. ```python theme={null} timber.warn("Rate limit approaching", {"current": 950, "limit": 1000, "endpoint": "/api/users"}) timber.warn("Deprecated API called", {"endpoint": "/v1/legacy", "recommendation": "Use /v2/modern instead"}) ``` ### `error(message, errorOrData?, options?)` Log errors. Accepts either an `Exception` object or a data dictionary. ```python theme={null} # With Exception object (extracts name, message, traceback) try: risky_operation() except Exception as e: timber.error("Operation failed", e) # With data object timber.error("Validation failed", {"field": "email", "value": "invalid", "reason": "Invalid email format"}) # With tags from timberlogs import LogOptions timber.error("Payment failed", e, LogOptions(tags=["payments", "critical"])) ``` When you pass an `Exception` object, the SDK automatically extracts: * `errorName` — The exception's class name (e.g. `ValueError`) * `errorStack` — The full traceback * The exception message is included in the data ### `log(entry)` Low-level logging method with full control over the log entry. ```python theme={null} from timberlogs import LogEntry timber.log(LogEntry( level="info", message="Custom log entry", data={"custom": "data", "nested": {"value": 123}}, user_id="user_123", session_id="sess_abc", request_id="req_xyz", tags=["important", "billing"], )) ``` ### Error Handling When you pass an `Exception` object to `error()`, the SDK extracts structured fields: ```python theme={null} try: risky_operation() except Exception as e: timber.error("Operation failed", e) # Creates a log entry with: # - error_name: "ValueError" (type(e).__name__) # - error_stack: full traceback string # - message includes the exception message ``` ### Data Object The `data` parameter accepts any JSON-serializable dictionary: ```python theme={null} timber.info("Request completed", { # Primitives "status": 200, "duration": 123.45, "cached": True, # Nested objects "user": { "id": "user_123", "role": "admin", }, # Arrays "permissions": ["read", "write", "delete"], }) ``` ### Tags Tags help categorize and filter logs. Add them via the options parameter: ```python theme={null} from timberlogs import LogOptions timber.info("Payment processed", {"amount": 99.99}, LogOptions(tags=["payments", "success"])) timber.error("Payment failed", e, LogOptions(tags=["payments", "critical", "pagerduty"])) ``` ## Flow Tracking Flows group related logs across a multi-step process with automatic flow IDs and step indexing. See [Flows](/concepts/flows) for a conceptual overview. ### Creating a Flow **Synchronous** — generates a local flow ID: ```python theme={null} flow = timber.flow("checkout") flow.info("Started checkout") flow.info("Validated cart", {"items": 3}) flow.info("Payment processed", {"amount": 99.99}) flow.info("Order confirmed", {"order_id": "ord_123"}) ``` **Asynchronous** — creates the flow on the server for a server-generated ID: ```python theme={null} flow = await timber.flow_async("checkout") flow.info("Started checkout") flow.info("Payment processed", {"amount": 99.99}) ``` Both produce logs with: * `flowId: "checkout-a1b2c3d4"` (auto-generated) * `stepIndex: 0, 1, 2, 3` (auto-incrementing) ### Flow Properties ```python theme={null} flow = timber.flow("user-onboarding") print(flow.id) # "user-onboarding-a1b2c3d4" print(flow.name) # "user-onboarding" ``` ### Flow Logging Methods Flows have the same logging methods as the main client: ```python theme={null} flow = timber.flow("data-pipeline") flow.debug("Debug info", {"stage": "init"}) flow.info("Processing started") flow.warn("Slow operation", {"duration": 5000}) flow.error("Processing failed", Exception("Timeout")) ``` ### Flow Chaining Flow methods return the flow for chaining: ```python theme={null} flow = timber.flow("checkout") flow.info("Cart validated").info("Payment authorized").info("Order created").info("Confirmation sent") ``` ### Real-World Examples #### Data Pipeline ```python theme={null} def process_pipeline(pipeline_id: str, records: list): flow = timber.flow(f"pipeline-{pipeline_id}") flow.info("Pipeline started", {"record_count": len(records)}) for i, record in enumerate(records): flow.debug("Processing record", {"index": i, "id": record["id"]}) transform(record) flow.info("Pipeline completed", {"processed": len(records)}) ``` ### Flow ID Generation Flow IDs are generated using the pattern: `{name}-{random8chars}` ``` checkout-a1b2c3d4 user-onboarding-x7y8z9ab api-request-mn0p1q2r ``` ### Level Filtering with Flows When using `min_level` configuration, filtered logs don't increment the step index: ```python theme={null} timber = create_timberlogs( # ... min_level="info", ) flow = timber.flow("example") flow.debug("Not sent") # Filtered, step_index not incremented flow.info("First log") # step_index: 0 flow.debug("Not sent") # Filtered, step_index not incremented flow.info("Second log") # step_index: 1 ``` This ensures your step indices remain sequential without gaps. ## Raw Format Ingestion Send pre-formatted log data directly to the ingestion endpoint, bypassing the structured log pipeline. Useful for forwarding logs from external systems (syslog daemons, CSV exports, JSONL streams). ### `ingest_raw(body, format, options?)` Synchronous version: ```python theme={null} timber.ingest_raw( "<165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused", "syslog", IngestRawOptions(source="syslog-relay", environment="production"), ) ``` ### `ingest_raw_async(body, format, options?)` (async) Asynchronous version: ```python theme={null} await timber.ingest_raw_async( "<165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused", "syslog", IngestRawOptions(source="syslog-relay", environment="production"), ) ``` **Parameters:** | Parameter | Type | Description | | ---------- | ------------------ | ----------------------------------------------------------------- | | `body` | `str` | The raw log data | | `format` | `FormatName` | One of `json`, `jsonl`, `syslog`, `text`, `csv`, `obl` | | `options?` | `IngestRawOptions` | Optional defaults for `source`, `environment`, `level`, `dataset` | The SDK sets the correct `Content-Type` header automatically and retries with exponential backoff on failure. ### Examples **CSV:** ```python theme={null} timber.ingest_raw( "level,message,source\nerror,Connection refused,api\ninfo,Request completed,api", "csv", IngestRawOptions(environment="production"), ) ``` **JSONL:** ```python theme={null} timber.ingest_raw( '{"level":"info","message":"line 1","source":"api","environment":"production"}\n{"level":"error","message":"line 2","source":"api","environment":"production"}', "jsonl", ) ``` **Plain text:** ```python theme={null} timber.ingest_raw( "2024-01-15 10:30:00 ERROR Connection refused\n2024-01-15 10:30:01 INFO Retrying...", "text", IngestRawOptions(source="nginx", environment="production"), ) ``` ### Supported Formats | Format | Content-Type | Description | | -------- | ---------------------- | ------------------------------- | | `json` | `application/json` | JSON array or `{ logs: [...] }` | | `jsonl` | `application/x-ndjson` | One JSON object per line | | `syslog` | `application/x-syslog` | RFC 5424 / RFC 3164 | | `text` | `text/plain` | One log per line | | `csv` | `text/csv` | Header row + data rows | | `obl` | `application/x-obl` | Open Board Logging | See [Log Ingestion](/api-reference/ingestion) for full format details. ## Client Methods ### `set_user_id(user_id)` Set the default user ID for subsequent logs. ```python theme={null} timber.set_user_id("user_123") ``` ### `set_session_id(session_id)` Set the default session ID for subsequent logs. ```python theme={null} timber.set_session_id("sess_abc") ``` ### `flush()` Immediately send all queued logs. ```python theme={null} timber.flush() ``` ### `flush_async()` (async) Asynchronously send all queued logs. ```python theme={null} await timber.flush_async() ``` ### `disconnect()` Flush logs and stop the auto-flush timer. ```python theme={null} timber.disconnect() ``` ### `disconnect_async()` (async) Asynchronously flush and stop the client. ```python theme={null} await timber.disconnect_async() ``` ## Method Chaining All methods return `self` for chaining: ```python theme={null} timber.set_user_id("user_123").set_session_id("sess_abc").info("User action") ``` ## Log Entry Dataclass ```python theme={null} @dataclass class LogEntry: level: Literal["debug", "info", "warn", "error"] message: str data: Optional[Dict[str, Any]] = None user_id: Optional[str] = None session_id: Optional[str] = None request_id: Optional[str] = None error_name: Optional[str] = None error_stack: Optional[str] = None tags: Optional[List[str]] = None flow_id: Optional[str] = None step_index: Optional[int] = None dataset: Optional[str] = None ip_address: Optional[str] = None country: Optional[str] = None ``` ## Options Object All logging methods accept an optional `options` object: ```python theme={null} from timberlogs import LogOptions timber.info("Message", {"data": "here"}, LogOptions( tags=["important", "billing"], )) ``` ## Context Manager Use the client as a context manager for automatic cleanup: ```python theme={null} with create_timberlogs( source="my-app", environment="production", api_key="tb_live_xxx", ) as timber: timber.info("Auto-flushed on exit") ``` ## Async Context Manager ```python theme={null} async with create_timberlogs( source="my-app", environment="production", api_key="tb_live_xxx", ) as timber: timber.info("Auto-flushed on exit") ``` ## Configuration ```python theme={null} timber = create_timberlogs( # Required source="my-app", environment="production", # development, staging, production api_key="tb_live_xxx", # Optional version="1.2.3", dataset="analytics", # Default dataset for log routing user_id="user_123", session_id="sess_abc", batch_size=10, # Logs to batch before sending flush_interval=5.0, # Auto-flush interval in seconds min_level="debug", # Minimum level to send on_error=lambda e: print(e), # Error callback max_retries=3, initial_delay_ms=1000, max_delay_ms=30000, ) ``` ## Requirements * Python 3.8+ * httpx >= 0.24.0 Ready to start logging? [Sign up free](https://app.timberlogs.dev) — send your first log in under 5 minutes. # Rust SDK Source: https://docs.timberlogs.dev/timberlogs-rust-sdk/sdks/rust The official Timberlogs SDK for Rust applications. ## Installation ```toml theme={null} [dependencies] timberlogs = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } ``` ## Basic Usage ```rust theme={null} use timberlogs::{TimberlogsClient, TimberlogsConfig, Environment}; #[tokio::main] async fn main() { let mut client = TimberlogsClient::new(TimberlogsConfig { source: "my-app".into(), environment: Environment::Production, api_key: std::env::var("TIMBER_API_KEY").unwrap(), ..Default::default() }); client.info("Hello, Timberlogs!", None).await.unwrap(); client.disconnect().await.unwrap(); } ``` ## Exports The crate exports the following: ```rust theme={null} use timberlogs::{ TimberlogsClient, // Client struct TimberlogsConfig, // Configuration struct RetryConfig, // Retry configuration Flow, // Flow struct for tracking LogEntry, // Log entry struct LogLevel, // Debug, Info, Warn, Error Environment, // Development, Staging, Production RawFormat, // Json, Jsonl, Syslog, Text, Csv, Obl IngestRawOptions, // Options for ingest_raw() TimberlogsError, // Error enum }; ``` ## Logging Methods ### `debug(message, data)` Log debug-level messages for detailed diagnostic information. ```rust theme={null} // Simple debug client.debug("Cache lookup", None).await?; // With data let mut data = HashMap::new(); data.insert("key".into(), json!("user:123")); data.insert("ttl".into(), json!(3600)); client.debug("Cache hit", Some(data)).await?; ``` ### `info(message, data)` Log informational messages about normal operations. ```rust theme={null} let mut data = HashMap::new(); data.insert("user_id".into(), json!("user_123")); data.insert("method".into(), json!("oauth")); client.info("User signed in", Some(data)).await?; ``` ### `warn(message, data)` Log warning conditions that might indicate a problem. ```rust theme={null} let mut data = HashMap::new(); data.insert("current".into(), json!(950)); data.insert("limit".into(), json!(1000)); client.warn("Rate limit approaching", Some(data)).await?; ``` ### `error(message, data)` Log errors. ```rust theme={null} let mut data = HashMap::new(); data.insert("field".into(), json!("email")); data.insert("reason".into(), json!("Invalid format")); client.error("Validation failed", Some(data)).await?; ``` ### `log(entry)` Low-level logging method with full control over the log entry. ```rust theme={null} client.log(LogEntry { level: LogLevel::Info, message: "Custom log entry".into(), data: Some(HashMap::from([ ("custom".into(), json!("data")), ])), user_id: Some("user_123".into()), session_id: Some("sess_abc".into()), request_id: Some("req_xyz".into()), tags: Some(vec!["important".into(), "billing".into()]), ..Default::default() }).await?; ``` ### Error Handling All async methods return `Result<(), TimberlogsError>`: ```rust theme={null} use timberlogs::TimberlogsError; match client.info("test", None).await { Ok(()) => {}, Err(TimberlogsError::Validation(msg)) => eprintln!("Invalid: {msg}"), Err(TimberlogsError::Http { status, body }) => eprintln!("HTTP {status}: {body}"), Err(TimberlogsError::Request(e)) => eprintln!("Network error: {e}"), Err(e) => eprintln!("Other: {e}"), } ``` ### Data Object The `data` parameter accepts `Option>`: ```rust theme={null} use std::collections::HashMap; use serde_json::json; let data = HashMap::from([ ("status".into(), json!(200)), ("duration".into(), json!(123.45)), ("cached".into(), json!(true)), ("user".into(), json!({"id": "user_123", "role": "admin"})), ("permissions".into(), json!(["read", "write", "delete"])), ]); client.info("Request completed", Some(data)).await?; ``` ### Tags Tags help categorize and filter logs. Add them via the `LogEntry`: ```rust theme={null} client.log(LogEntry { level: LogLevel::Info, message: "Payment processed".into(), tags: Some(vec!["payments".into(), "success".into()]), ..Default::default() }).await?; ``` ## Flow Tracking Flows group related logs across a multi-step process with automatic flow IDs and step indexing. ### Creating a Flow Use the `flow()` method to create a new flow. This is an async operation that creates the flow on the server: ```rust theme={null} let mut flow = client.flow("checkout").await?; flow.info("Started checkout", None).await?; flow.info("Validated cart", Some(HashMap::from([ ("items".into(), json!(3)), ]))).await?; flow.info("Payment processed", Some(HashMap::from([ ("amount".into(), json!(99.99)), ]))).await?; flow.info("Order confirmed", None).await?; ``` Each log is automatically tagged with: * `flow_id` (server-generated) * `step_index: 0, 1, 2, 3` (auto-incrementing) ### Flow Properties ```rust theme={null} let flow = client.flow("user-onboarding").await?; println!("{}", flow.id); // "user-onboarding-x7y8z9a0" println!("{}", flow.name); // "user-onboarding" println!("{}", flow.step_index()); // 0 ``` ### Flow Logging Methods Flows have the same logging methods as the main client: ```rust theme={null} let mut flow = client.flow("data-pipeline").await?; flow.debug("Debug info", None).await?; flow.info("Processing started", None).await?; flow.warn("Slow operation", Some(HashMap::from([ ("duration".into(), json!(5000)), ]))).await?; flow.error("Processing failed", None).await?; ``` ### Custom Level and Tags Use `log_with_level` for full control: ```rust theme={null} flow.log_with_level( LogLevel::Info, "Step complete", Some(HashMap::from([("stage".into(), json!("init"))])), Some(vec!["pipeline".into()]), ).await?; ``` ### Level Filtering with Flows When using `min_level` configuration, filtered logs don't increment the step index: ```rust theme={null} let client = TimberlogsClient::new(TimberlogsConfig { min_level: Some(LogLevel::Info), ..Default::default() }); let mut flow = client.flow("example").await?; flow.debug("Not sent", None).await?; // Filtered, step_index not incremented flow.info("First log", None).await?; // step_index: 0 flow.debug("Not sent", None).await?; // Filtered, step_index not incremented flow.info("Second log", None).await?; // step_index: 1 ``` This ensures your step indices remain sequential without gaps. ## Raw Format Ingestion Send pre-formatted log data directly to the ingestion endpoint, bypassing the structured log pipeline. ### `ingest_raw(body, format, options)` ```rust theme={null} use timberlogs::{RawFormat, IngestRawOptions}; client.ingest_raw( "<165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused", RawFormat::Syslog, Some(IngestRawOptions { source: Some("syslog-relay".into()), environment: Some(Environment::Production), ..Default::default() }), ).await?; ``` **Parameters:** | Parameter | Type | Description | | --------- | -------------------------- | ----------------------------------------------------------------- | | `body` | `impl Into` | The raw log data | | `format` | `RawFormat` | One of `Json`, `Jsonl`, `Syslog`, `Text`, `Csv`, `Obl` | | `options` | `Option` | Optional defaults for `source`, `environment`, `level`, `dataset` | ### Examples **CSV:** ```rust theme={null} client.ingest_raw( "level,message,source\nerror,Connection refused,api\ninfo,Request completed,api", RawFormat::Csv, Some(IngestRawOptions { environment: Some(Environment::Production), ..Default::default() }), ).await?; ``` **JSONL:** ```rust theme={null} client.ingest_raw( "{\"level\":\"info\",\"message\":\"line 1\"}\n{\"level\":\"error\",\"message\":\"line 2\"}", RawFormat::Jsonl, None, ).await?; ``` **Plain text:** ```rust theme={null} client.ingest_raw( "2024-01-15 10:30:00 ERROR Connection refused\n2024-01-15 10:30:01 INFO Retrying...", RawFormat::Text, Some(IngestRawOptions { source: Some("nginx".into()), environment: Some(Environment::Production), ..Default::default() }), ).await?; ``` ### Supported Formats | Format | Content-Type | Description | | -------- | ---------------------- | ------------------------ | | `Json` | `application/json` | JSON array or object | | `Jsonl` | `application/x-ndjson` | One JSON object per line | | `Syslog` | `application/x-syslog` | RFC 5424 / RFC 3164 | | `Text` | `text/plain` | One log per line | | `Csv` | `text/csv` | Header row + data rows | | `Obl` | `application/x-obl` | Open Board Logging | ## Client Methods ### `set_user_id(user_id)` Set the default user ID for subsequent logs. ```rust theme={null} client.set_user_id(Some("user_123".into())).await; ``` ### `set_session_id(session_id)` Set the default session ID for subsequent logs. ```rust theme={null} client.set_session_id(Some("sess_abc".into())).await; ``` ### `flush()` Immediately send all queued logs. ```rust theme={null} client.flush().await?; ``` ### `disconnect()` Flush logs and stop the auto-flush timer. Always call this before your application exits. ```rust theme={null} client.disconnect().await?; ``` ## Log Entry Struct ```rust theme={null} pub struct LogEntry { pub level: LogLevel, pub message: String, pub data: Option>, pub user_id: Option, pub session_id: Option, pub request_id: Option, pub error_name: Option, pub error_stack: Option, pub tags: Option>, pub flow_id: Option, pub step_index: Option, pub dataset: Option, pub timestamp: Option, // Unix ms, defaults to now pub ip_address: Option, pub country: Option, } ``` Ready to start logging? [Sign up free](https://app.timberlogs.dev) — send your first log in under 5 minutes. # Configuration Source: https://docs.timberlogs.dev/timberlogs-rust-sdk/sdks/rust-configuration Full reference for all Rust SDK configuration options. ## Required Options ```rust theme={null} use timberlogs::{TimberlogsClient, TimberlogsConfig, Environment}; let client = TimberlogsClient::new(TimberlogsConfig { source: "my-app".into(), // Required: identifies your app/service environment: Environment::Production, // Required: Development, Staging, or Production api_key: "tb_live_xxx".into(), // Required for HTTP transport ..Default::default() }); ``` | Option | Type | Description | | ------------- | ------------- | --------------------------------------------------------------- | | `source` | `String` | Your application or service name. Used to filter logs. | | `environment` | `Environment` | `Development`, `Staging`, or `Production`. | | `api_key` | `String` | Your Timberlogs API key (starts with `tb_live_` or `tb_test_`). | ## Optional Options ```rust theme={null} use timberlogs::{TimberlogsClient, TimberlogsConfig, Environment, LogLevel, RetryConfig}; let client = TimberlogsClient::new(TimberlogsConfig { source: "my-app".into(), environment: Environment::Production, api_key: std::env::var("TIMBER_API_KEY").unwrap(), // Optional settings version: Some("1.2.3".into()), dataset: Some("analytics".into()), user_id: Some("user_123".into()), session_id: Some("sess_abc".into()), batch_size: Some(10), flush_interval_ms: Some(5000), min_level: Some(LogLevel::Info), on_error: Some(Box::new(|err| { eprintln!("Timberlogs error: {err}"); })), retry: Some(RetryConfig { max_retries: 3, initial_delay_ms: 1000, max_delay_ms: 30000, }), ..Default::default() }); ``` | Option | Type | Default | Description | | ------------------- | ----------------------- | --------- | --------------------------------------------------------- | | `version` | `Option` | `None` | Your application version (e.g., `"1.2.3"`). | | `dataset` | `Option` | `None` | Default dataset for grouping/routing logs. | | `user_id` | `Option` | `None` | Default user ID attached to all logs. | | `session_id` | `Option` | `None` | Default session ID attached to all logs. | | `batch_size` | `Option` | `10` | Number of logs to batch before sending. Must be > 0. | | `flush_interval_ms` | `Option` | `5000` | Milliseconds between auto-flush. | | `min_level` | `Option` | `Debug` | Minimum level to send (`Debug`, `Info`, `Warn`, `Error`). | | `on_error` | `Option` | `None` | Called when background flush fails after all retries. | | `retry` | `Option` | See below | Retry configuration for failed requests. | ## Retry Configuration The SDK automatically retries failed requests with exponential backoff. ```rust theme={null} use timberlogs::RetryConfig; RetryConfig { max_retries: 3, // Number of retry attempts initial_delay_ms: 1000, // First retry delay max_delay_ms: 30000, // Maximum delay between retries } ``` ## Log Level Filtering Use `min_level` to filter out lower-priority logs: ```rust theme={null} let client = TimberlogsClient::new(TimberlogsConfig { min_level: Some(LogLevel::Warn), // Only send warn and error logs ..Default::default() }); client.debug("This will NOT be sent", None).await?; client.info("This will NOT be sent", None).await?; client.warn("This WILL be sent", None).await?; client.error("This WILL be sent", None).await?; ``` ## Setting User/Session at Runtime You can update the user and session IDs after initialization: ```rust theme={null} // After user logs in client.set_user_id(Some("user_123".into())).await; client.set_session_id(Some("sess_abc".into())).await; // Clear on logout client.set_user_id(None).await; client.set_session_id(None).await; ``` ## Validation Limits The SDK validates fields before sending. Logs that exceed these limits will return a `TimberlogsError::Validation` error. | Field | Limit | | ------------------------------------- | -------------------------------- | | `message` | 1–10,000 characters | | `error_name` | Max 200 characters | | `error_stack` | Max 10,000 characters | | `user_id`, `session_id`, `request_id` | Max 100 characters each | | `flow_id`, `dataset` | Max 50 characters each | | `ip_address` | Max 100 characters | | `country` | Max 10 characters | | `step_index` | 0–1,000 | | `tags` | Max 20 items, 50 characters each | ## Environment Variables We recommend storing sensitive values in environment variables: ```bash theme={null} # .env TIMBER_API_KEY=tb_live_xxxxxxxxxxxxx ``` ```rust theme={null} let client = TimberlogsClient::new(TimberlogsConfig { source: "my-app".into(), environment: Environment::Production, api_key: std::env::var("TIMBER_API_KEY").expect("TIMBER_API_KEY required"), ..Default::default() }); ``` # Configuration Source: https://docs.timberlogs.dev/timberlogs-typescript-sdk/sdks/configuration Full reference for all SDK configuration options. ## Required Options ```typescript TypeScript theme={null} const timber = createTimberlogs({ source: 'my-app', // Required: identifies your app/service environment: 'production', // Required: development, staging, or production apiKey: 'tb_live_xxx', // Required for HTTP transport }) ``` ```python Python theme={null} from timberlogs import create_timberlogs timber = create_timberlogs( source="my-app", # Required: identifies your app/service environment="production", # Required: development, staging, or production api_key="tb_live_xxx", # Required for HTTP transport ) ``` | Option | Type | Description | | ------------- | -------------------------------------------- | --------------------------------------------------------------- | | `source` | `string` | Your application or service name. Used to filter logs. | | `environment` | `'development' \| 'staging' \| 'production'` | The environment your app is running in. | | `apiKey` | `string` | Your Timberlogs API key (starts with `tb_live_` or `tb_test_`). | ## Optional Options ```typescript TypeScript theme={null} const timber = createTimberlogs({ source: 'my-app', environment: 'production', apiKey: process.env.TIMBER_API_KEY, // Optional settings version: '1.2.3', // Your app version dataset: 'analytics', // Default dataset for log routing userId: 'user_123', // Default user ID for all logs sessionId: 'sess_abc', // Default session ID batchSize: 10, // Logs to batch before sending flushInterval: 5000, // Auto-flush interval in ms minLevel: 'info', // Minimum log level to send onError: (err) => {}, // Error callback retry: { maxRetries: 3, initialDelayMs: 1000, maxDelayMs: 30000, }, }) ``` ```python Python theme={null} timber = create_timberlogs( source="my-app", environment="production", api_key=os.environ["TIMBER_API_KEY"], # Optional settings version="1.2.3", dataset="analytics", user_id="user_123", session_id="sess_abc", batch_size=10, flush_interval=5.0, # Auto-flush interval in seconds min_level="info", on_error=lambda e: print(e), max_retries=3, initial_delay_ms=1000, max_delay_ms=30000, ) ``` | Option | Type | Default | Description | | --------------- | ---------- | ----------- | --------------------------------------------------------- | | `version` | `string` | - | Your application version (e.g., `'1.2.3'`). | | `dataset` | `string` | `'default'` | Default dataset for grouping/routing logs. | | `userId` | `string` | - | Default user ID attached to all logs. | | `sessionId` | `string` | - | Default session ID attached to all logs. | | `batchSize` | `number` | `10` | Number of logs to batch before sending. | | `flushInterval` | `number` | `5000` | Milliseconds between auto-flush. | | `minLevel` | `LogLevel` | `'debug'` | Minimum level to send (`debug`, `info`, `warn`, `error`). | | `onError` | `function` | - | Called when sending fails after all retries. | | `retry` | `object` | See below | Retry configuration for failed requests. | ## Retry Configuration The SDK automatically retries failed requests with exponential backoff. ```typescript theme={null} { retry: { maxRetries: 3, // Number of retry attempts initialDelayMs: 1000, // First retry delay maxDelayMs: 30000, // Maximum delay between retries } } ``` ## Log Level Filtering Use `minLevel` to filter out lower-priority logs: ```typescript TypeScript theme={null} const timber = createTimberlogs({ // ... minLevel: 'warn', // Only send warn and error logs }) timber.debug('This will NOT be sent') timber.info('This will NOT be sent') timber.warn('This WILL be sent') timber.error('This WILL be sent') ``` ```python Python theme={null} timber = create_timberlogs( # ... min_level="warn", # Only send warn and error logs ) timber.debug("This will NOT be sent") timber.info("This will NOT be sent") timber.warn("This WILL be sent") timber.error("This WILL be sent") ``` ## Setting User/Session at Runtime You can update the user and session IDs after initialization: ```typescript TypeScript theme={null} // After user logs in timber.setUserId('user_123') timber.setSessionId('sess_abc') // Clear on logout timber.setUserId(undefined) timber.setSessionId(undefined) ``` ```python Python theme={null} # After user logs in timber.set_user_id("user_123") timber.set_session_id("sess_abc") # Clear on logout timber.set_user_id(None) timber.set_session_id(None) ``` ## Validation Limits Both SDKs validate fields before sending. Logs that exceed these limits will be rejected. | Field | Limit | | ---------------------------------- | -------------------------------- | | `message` | 1–10,000 characters | | `source` | 1–100 characters | | `errorName` | Max 200 characters | | `errorStack` | Max 10,000 characters | | `userId`, `sessionId`, `requestId` | Max 100 characters each | | `flowId`, `dataset` | Max 50 characters each | | `stepIndex` | 0–1,000 | | `tags` | Max 20 items, 50 characters each | ## Environment Variables We recommend storing sensitive values in environment variables: ```bash theme={null} # .env TIMBER_API_KEY=tb_live_xxxxxxxxxxxxx ``` ```typescript TypeScript theme={null} const timber = createTimberlogs({ source: 'my-app', environment: process.env.NODE_ENV || 'development', apiKey: process.env.TIMBER_API_KEY, }) ``` ```python Python theme={null} import os timber = create_timberlogs( source="my-app", environment=os.environ.get("PYTHON_ENV", "development"), api_key=os.environ["TIMBER_API_KEY"], ) ``` # TypeScript SDK Source: https://docs.timberlogs.dev/timberlogs-typescript-sdk/sdks/typescript The official Timberlogs SDK for TypeScript and JavaScript applications. ## Installation ```bash theme={null} npm install timberlogs-client ``` ## Basic Usage ```typescript theme={null} import { createTimberlogs } from 'timberlogs-client' const timber = createTimberlogs({ source: 'my-app', environment: 'production', apiKey: process.env.TIMBER_API_KEY, dataset: 'analytics', // Optional: default dataset for log routing }) timber.info('Hello, Timberlogs!') ``` ## Exports The SDK exports the following: ```typescript theme={null} import { createTimberlogs, // Factory function TimberlogsClient, // Client class Flow, // Flow class for tracking } from 'timberlogs-client' // Types import type { LogLevel, // 'debug' | 'info' | 'warn' | 'error' Environment, // 'development' | 'staging' | 'production' FormatName, // 'json' | 'jsonl' | 'syslog' | 'text' | 'csv' | 'obl' IngestRawOptions, // Options for ingestRaw() LogEntry, // Log entry interface TimberlogsConfig, // Configuration interface } from 'timberlogs-client' ``` ## Logging Methods ### `debug(message, data?, options?)` Log debug-level messages for detailed diagnostic information. ```typescript theme={null} // Simple debug timber.debug('Cache lookup') // With data timber.debug('Cache hit', { key: 'user:123', ttl: 3600, }) // With tags timber.debug('Cache miss', { key: 'user:456' }, { tags: ['cache', 'performance'], }) ``` ### `info(message, data?, options?)` Log informational messages about normal operations. ```typescript theme={null} timber.info('User signed in', { userId: 'user_123', method: 'oauth', }) timber.info('Order placed', { orderId: 'ord_xyz', total: 99.99, }) ``` ### `warn(message, data?, options?)` Log warning conditions that might indicate a problem. ```typescript theme={null} timber.warn('Rate limit approaching', { current: 950, limit: 1000, endpoint: '/api/users', }) timber.warn('Deprecated API called', { endpoint: '/v1/legacy', recommendation: 'Use /v2/modern instead', }) ``` ### `error(message, errorOrData?, options?)` Log errors. Accepts either an `Error` object or a data object. ```typescript theme={null} // With Error object (extracts name, message, stack) try { await riskyOperation() } catch (err) { timber.error('Operation failed', err) } // With data object timber.error('Validation failed', { field: 'email', value: 'invalid', reason: 'Invalid email format', }) // With tags timber.error('Payment failed', error, { tags: ['payments', 'critical'], }) ``` When you pass an `Error` object, the SDK automatically extracts: * `errorName` — The error's class name (e.g. `TypeError`) * `errorStack` — The full stack trace * The error `message` is included in the data ### `log(entry)` Low-level logging method with full control over the log entry. ```typescript theme={null} timber.log({ level: 'info', message: 'Custom log entry', data: { custom: 'data', nested: { value: 123 }, }, userId: 'user_123', sessionId: 'sess_abc', requestId: 'req_xyz', tags: ['important', 'billing'], }) ``` ### Error Handling When you pass an `Error` object to `error()`, the SDK extracts structured fields: ```typescript theme={null} try { await riskyOperation() } catch (err) { timber.error('Operation failed', err) // Creates a log entry with: // - errorName: "TypeError" (err.name) // - errorStack: "TypeError: Cannot read..." (err.stack) // - message includes the error message } ``` ### Data Object The `data` parameter accepts any JSON-serializable object: ```typescript theme={null} timber.info('Request completed', { // Primitives status: 200, duration: 123.45, cached: true, // Nested objects user: { id: 'user_123', role: 'admin', }, // Arrays permissions: ['read', 'write', 'delete'], }) ``` ### Tags Tags help categorize and filter logs. Add them via the options parameter: ```typescript theme={null} timber.info('Payment processed', { amount: 99.99 }, { tags: ['payments', 'success'], }) timber.error('Payment failed', error, { tags: ['payments', 'critical', 'pagerduty'], }) ``` ## Flow Tracking Flows group related logs across a multi-step process with automatic flow IDs and step indexing. See [Flows](/concepts/flows) for a conceptual overview. ### Creating a Flow Use the `flow()` method to create a new flow. This is an async operation that creates the flow on the server: ```typescript theme={null} const flow = await timber.flow('checkout') flow.info('Started checkout') flow.info('Validated cart', { items: 3 }) flow.info('Payment processed', { amount: 99.99 }) flow.info('Order confirmed', { orderId: 'ord_123' }) ``` Each log is automatically tagged with: * `flowId: "checkout-a1b2c3d4"` (auto-generated) * `stepIndex: 0, 1, 2, 3` (auto-incrementing) ### Flow Properties ```typescript theme={null} const flow = await timber.flow('user-onboarding') console.log(flow.id) // "user-onboarding-x7y8z9a0" console.log(flow.name) // "user-onboarding" ``` ### Flow Logging Methods Flows have the same logging methods as the main client: ```typescript theme={null} const flow = await timber.flow('data-pipeline') flow.debug('Debug info', { stage: 'init' }) flow.info('Processing started') flow.warn('Slow operation', { duration: 5000 }) flow.error('Processing failed', new Error('Timeout')) ``` ### Flow Chaining Flow methods return the flow for chaining: ```typescript theme={null} const flow = await timber.flow('checkout') flow .info('Cart validated') .info('Payment authorized') .info('Order created') .info('Confirmation sent') ``` ### Real-World Examples #### E-commerce Checkout ```typescript theme={null} async function processCheckout(cart: Cart, user: User) { const flow = await timber.flow('checkout') flow.info('Checkout started', { userId: user.id, itemCount: cart.items.length }) // Validate cart const validation = await validateCart(cart) if (!validation.valid) { flow.warn('Cart validation failed', { errors: validation.errors }) return { success: false } } flow.info('Cart validated') // Process payment try { const payment = await processPayment(cart.total, user.paymentMethod) flow.info('Payment processed', { transactionId: payment.id, amount: cart.total }) } catch (error) { flow.error('Payment failed', error) return { success: false } } // Create order const order = await createOrder(cart, user) flow.info('Order created', { orderId: order.id }) // Send confirmation await sendConfirmationEmail(user.email, order) flow.info('Confirmation sent', { email: user.email }) return { success: true, orderId: order.id } } ``` #### API Request Lifecycle ```typescript theme={null} app.use(async (req, res, next) => { const flow = await timber.flow('api-request') req.flow = flow flow.info('Request received', { method: req.method, path: req.path, ip: req.ip, }) const start = Date.now() res.on('finish', () => { flow.info('Response sent', { status: res.statusCode, duration: Date.now() - start, }) }) next() }) // In route handlers app.post('/users', async (req, res) => { req.flow.info('Creating user', { email: req.body.email }) const user = await createUser(req.body) req.flow.info('User created', { userId: user.id }) res.json(user) }) ``` #### Background Job ```typescript theme={null} async function processJob(job: Job) { const flow = await timber.flow(`job-${job.type}`) flow.info('Job started', { jobId: job.id, type: job.type }) for (const item of job.items) { flow.debug('Processing item', { itemId: item.id }) await processItem(item) } flow.info('Job completed', { jobId: job.id, processedCount: job.items.length }) } ``` ### Flow ID Generation Flow IDs are generated server-side using the pattern: `{name}-{random8chars}` ``` checkout-a1b2c3d4 user-onboarding-x7y8z9ab api-request-mn0p1q2r ``` ### Level Filtering with Flows When using `minLevel` configuration, filtered logs don't increment the step index: ```typescript theme={null} const timber = createTimberlogs({ // ... minLevel: 'info', }) const flow = await timber.flow('example') flow.debug('Not sent') // Filtered, stepIndex not incremented flow.info('First log') // stepIndex: 0 flow.debug('Not sent') // Filtered, stepIndex not incremented flow.info('Second log') // stepIndex: 1 ``` This ensures your step indices remain sequential without gaps. ## Raw Format Ingestion Send pre-formatted log data directly to the ingestion endpoint, bypassing the structured log pipeline. Useful for forwarding logs from external systems (syslog daemons, CSV exports, JSONL streams). ### `ingestRaw(body, format, options?)` ```typescript theme={null} await timber.ingestRaw( '<165>1 2024-01-15T10:30:00.000Z myhost api 1234 - - Connection refused', 'syslog', { source: 'syslog-relay', environment: 'production' } ) ``` **Parameters:** | Parameter | Type | Description | | ---------- | ------------------ | ----------------------------------------------------------------- | | `body` | `string` | The raw log data | | `format` | `FormatName` | One of `json`, `jsonl`, `syslog`, `text`, `csv`, `obl` | | `options?` | `IngestRawOptions` | Optional defaults for `source`, `environment`, `level`, `dataset` | The SDK sets the correct `Content-Type` header automatically and retries with exponential backoff on failure. ### Examples **CSV:** ```typescript theme={null} await timber.ingestRaw( 'level,message,source\nerror,Connection refused,api\ninfo,Request completed,api', 'csv', { environment: 'production' } ) ``` **JSONL:** ```typescript theme={null} await timber.ingestRaw( '{"level":"info","message":"line 1","source":"api","environment":"production"}\n{"level":"error","message":"line 2","source":"api","environment":"production"}', 'jsonl' ) ``` **Plain text:** ```typescript theme={null} await timber.ingestRaw( '2024-01-15 10:30:00 ERROR Connection refused\n2024-01-15 10:30:01 INFO Retrying...', 'text', { source: 'nginx', environment: 'production' } ) ``` ### Supported Formats | Format | Content-Type | Description | | -------- | ---------------------- | ------------------------------- | | `json` | `application/json` | JSON array or `{ logs: [...] }` | | `jsonl` | `application/x-ndjson` | One JSON object per line | | `syslog` | `application/x-syslog` | RFC 5424 / RFC 3164 | | `text` | `text/plain` | One log per line | | `csv` | `text/csv` | Header row + data rows | | `obl` | `application/x-obl` | Open Board Logging | See [Log Ingestion](/api-reference/ingestion) for full format details. ## Client Methods ### `setUserId(userId)` Set the default user ID for subsequent logs. ```typescript theme={null} timber.setUserId('user_123') ``` ### `setSessionId(sessionId)` Set the default session ID for subsequent logs. ```typescript theme={null} timber.setSessionId('sess_abc') ``` ### `flush()` Immediately send all queued logs. ```typescript theme={null} await timber.flush() ``` ### `disconnect()` Flush logs and stop the auto-flush timer. ```typescript theme={null} await timber.disconnect() ``` ## Method Chaining All methods return `this` for chaining: ```typescript theme={null} timber .setUserId('user_123') .setSessionId('sess_abc') .info('User action') .debug('Debug info') ``` ## Log Entry Interface ```typescript theme={null} interface LogEntry { level: 'debug' | 'info' | 'warn' | 'error' message: string data?: Record userId?: string sessionId?: string requestId?: string errorName?: string errorStack?: string tags?: string[] flowId?: string stepIndex?: number dataset?: string timestamp?: string ipAddress?: string country?: string } ``` ## Options Object All logging methods accept an optional `options` object: ```typescript theme={null} timber.info('Message', { data: 'here' }, { tags: ['important', 'billing'], }) ``` Ready to start logging? [Sign up free](https://app.timberlogs.dev) — send your first log in under 5 minutes.