npm package: @streamlogia/javascript-sdk
Description: JavaScript client for sending structured logs to the Streamlogia Log Ingestor API
Author: Streamlogia
Latest version: 1.0.0

# Streamlogia JavaScript SDK

A JavaScript client library for sending structured logs to the [Streamlogia](https://streamlogia.com) Log Ingestor API.

## Features

- Structured log ingestion (DEBUG, INFO, WARN, ERROR)
- Asynchronous batching with configurable flush interval and batch size
- Winston transport integration
- Pino destination stream integration
- Express middleware for automatic request/response logging (method, path, status, duration, response size)
- Next.js Edge Runtime compatible
- Works in Node.js 18+ with native `fetch` — no extra dependencies required

## Requirements

Node.js 18 or later (uses native `fetch`). For older Node.js, pass a custom `fetch` via the `fetch` option.

## Installation

```sh
npm install @streamlogia/javascript-sdk
```

## Quick Start

```js
import { LogIngestorClient } from '@streamlogia/javascript-sdk'

const client = new LogIngestorClient({
  apiKey:    process.env.LOGINGESTOR_API_KEY,
  projectId: process.env.LOGINGESTOR_PROJECT_ID,
  source:    'my-service',
})

await client.info('application started')
await client.info('user signed in', { meta: { userId: 'u_123' }, tags: ['auth'] })

// Flush remaining logs and stop the background timer before exit
await client.close()
```

## Usage

### Direct Client Logging

The client exposes a method for each log level:

```js
client.debug('cache miss',             { meta: { key: 'session:42' } })
client.info('order placed',            { meta: { orderId: 'ord_99', total: 49.95 }, tags: ['orders'] })
client.warn('rate limit approaching',  { meta: { remaining: 5 } })
client.error('payment failed',         { meta: { error: err.message }, tags: ['payments'] })
```

Each method accepts:

- `message` — log message string
- `opts.meta` — optional key/value metadata object
- `opts.tags` — optional string array for filtering
- `opts.source` — override the default source for this entry

### Winston Integration

`createWinstonTransport` adapts the client to a Winston transport — analogous to `NewSlogHandler` in the Go SDK. Pass the `Transport` base class from `winston-transport` so the SDK stays free of hard dependencies.

```js
import Transport from 'winston-transport'
import winston from 'winston'
import { LogIngestorClient } from '@streamlogia/javascript-sdk'

const client = new LogIngestorClient({ /* ... */ })
const LogTransport = client.createWinstonTransport(Transport)

const logger = winston.createLogger({
  level: 'debug',
  transports: [
    new winston.transports.Console({ format: winston.format.simple() }),
    new LogTransport(),
  ],
})

logger.info('order placed', { orderId: 'ord_123', amount: 49.99 })
logger.error('payment failed', { customerId: 'c_42', error: err.message })
```

Adding `Console` alongside `LogTransport` fans logs out to both stdout and the ingestor simultaneously — the same pattern as `MultiHandler` in the Go SDK. Stdout is captured by systemd and visible via `journalctl -u your-service -f`.

> **Note:** logs sent via `logger.*` (Winston) go to both destinations. Logs generated by `expressMiddleware()` are written directly to the client and do **not** pass through Winston — they reach the console via the client's built-in console mirroring (`console: true` by default). Set `console: false` to suppress them:
>
> ```js
> const client = new LogIngestorClient({ ..., console: false })
> ```
>
> | Log source | Console (stdout) | Ingestor |
> | --------------------------------- | :--------------: | :------: |
> | `logger.info/warn/error/...`      | ✅ via Winston | ✅ |
> | `expressMiddleware()` access logs | ✅ via client | ✅ |

### Pino Integration

`pinoDestination()` returns a `Writable` stream that consumes Pino's NDJSON output and forwards each record to the ingestor:

```js
import pino from 'pino'
import { LogIngestorClient } from '@streamlogia/javascript-sdk'

const client = new LogIngestorClient({ /* ... */ })

const logger = pino({ level: 'debug' }, pino.multistream([
  { stream: process.stdout },             // stdout
  { stream: client.pinoDestination() },   // ingestor
]))

logger.info({ orderId: 'ord_123' }, 'order placed')
logger.error({ customerId: 'c_42', err }, 'payment failed')
```

### Express Middleware

Mount `expressMiddleware()` once at the top of your Express app. It automatically logs one entry per request — method, path, status code, duration, response size, IP, user agent, and `X-Request-Id` (if present). No per-route code needed.

```js
import express from 'express'
import { LogIngestorClient } from '@streamlogia/javascript-sdk'

const client = new LogIngestorClient({ /* ... */ })
const app = express()

app.use(express.json())
app.use(client.expressMiddleware())
```

The log level is determined by the response status code:

| Status range | Level |
| ------------ | ----- |
| 2xx / 3xx    | INFO  |
| 4xx          | WARN  |
| 5xx          | ERROR |

**Options:**

```js
app.use(client.expressMiddleware({ logBody: true }))
```

| Option      | Default | Description                                  |
| ----------- | ------- | -------------------------------------------- |
| `logBody`   | `false` | Include the parsed request body in `meta`    |

### Next.js Edge Middleware

Place this at the root of your Next.js project as `middleware.js`. It runs on the Edge Runtime where response lifecycle hooks are not available, so the request is logged on the way in.

```js
// middleware.js
import { NextResponse } from 'next/server'

const API_KEY    = process.env.LOGINGESTOR_API_KEY
const PROJECT_ID = process.env.LOGINGESTOR_PROJECT_ID

export async function middleware(request) {
  const entry = {
    projectId: PROJECT_ID,
    level:     'INFO',
    message:   `${request.method} ${request.nextUrl.pathname}`,
    source:    'nextjs-edge',
    timestamp: new Date().toISOString(),
    tags:      ['http', 'nextjs'],
    meta: {
      method:    request.method,
      path:      request.nextUrl.pathname,
      userAgent: request.headers.get('user-agent'),
    },
  }

  // Fire-and-forget — never let logging block the response
  fetch('https://api.streamlogia.com/v1/ingest', {
    method:  'POST',
    headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
    body:    JSON.stringify([entry]),
  }).catch(() => {})

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
```

See [`examples/nextjs-middleware.js`](examples/nextjs-middleware.js) for the full version.

## Configuration

```js
const client = new LogIngestorClient({
  apiKey:          process.env.LOGINGESTOR_API_KEY,
  projectId:       process.env.LOGINGESTOR_PROJECT_ID,
  source:          'order-service',
  batchSize:       50,
  flushIntervalMs: 10_000,
  fetch:           customFetchFn,      // optional: custom fetch implementation
  onError:         (err) => { ... },   // optional: called when an ingest request fails
  console:         true,               // optional: mirror every log to the console
})
```

| Option            | Default                         | Description                                          |
| ----------------- | ------------------------------- | ---------------------------------------------------- |
| `apiKey`          | —                               | API key (required)                                   |
| `projectId`       | —                               | Project UUID (required)                              |
| `baseURL`         | `"https://api.streamlogia.com"` | Override the ingestor API base URL                   |
| `source`          | `"unknown"`                     | Default `source` field on every log entry            |
| `batchSize`       | `1`                             | Flush automatically after accumulating `n` entries   |
| `flushIntervalMs` | `5000`                          | Background flush interval in milliseconds            |
| `fetch`           | `globalThis.fetch`              | Custom fetch implementation                          |
| `onError`         | `console.error`                 | Called when an ingest HTTP request fails             |
| `console`         | `true`                          | Mirror every log entry to the console as well        |

## Graceful Shutdown

Call `client.close()` before your process exits to flush any buffered entries and stop the background timer.

```js
process.on('SIGTERM', async () => {
  await client.close()
  process.exit(0)
})
```

## API Reference

### `new LogIngestorClient(opts)`

Creates the client and starts the background flush timer.

### Log methods

| Method                              | Description              |
| ----------------------------------- | ------------------------ |
| `client.debug(message, opts?)`      | Log at DEBUG level       |
| `client.info(message, opts?)`       | Log at INFO level        |
| `client.warn(message, opts?)`       | Log at WARN level        |
| `client.error(message, opts?)`      | Log at ERROR level       |

### Other methods

| Method                                    | Description                                              |
| ----------------------------------------- | -------------------------------------------------------- |
| `client.ingest(entries)`                  | Send entries immediately, bypassing the queue            |
| `client.flush()`                          | Drain the internal queue                                 |
| `client.close()`                          | Flush and stop the background timer                      |
| `client.expressMiddleware(opts?)`         | Express middleware for automatic request logging         |
| `client.createWinstonTransport(Transport)`| Returns a Winston transport class bound to this client   |
| `client.pinoDestination()`                | Returns a Pino-compatible `Writable` destination stream  |

### Log entry shape

```js
{
  projectId: string,
  level:     'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
  message:   string,
  source:    string,
  timestamp: string,   // ISO 8601
  tags:      string[],
  meta:      object,
}
```

## Examples

| File | Description |
| ---- | ----------- |
| [`examples/express-app.js`](examples/express-app.js) | Express app with Winston integration and request middleware |
| [`examples/nextjs-middleware.js`](examples/nextjs-middleware.js) | Next.js Edge Runtime middleware |

Run the Express example:

```sh
LOGINGESTOR_API_KEY=<key> LOGINGESTOR_PROJECT_ID=<id> node examples/express-app.js
```

## License

[MIT](LICENSE)