---
url: https://simpledatalogs.com/docs/quickstarts/node.md
description: >-
  Send events, logs and crashes from Node to simpledatalogs in one minute, with
  pino and winston recipes.
---

# Node

## Install

```sh
npm install simpledatalogs
npx simpledatalogs init   # adds SIMPLEDATALOGS_KEY to .env and writes simpledatalogs.js (.mjs in CommonJS projects)
```

Use a **secret key** (`sdl_sec_…`) on servers.

## Add it

```js
// simpledatalogs.js
import { init } from 'simpledatalogs'

export const sdl = init({ key: process.env.SIMPLEDATALOGS_KEY, autocapture: true })
```

```js
import { sdl } from './simpledatalogs.js'

sdl.track('job_finished', { queue: 'emails', duration_ms: 812 })
sdl.log('error', 'Payment failed', { order_id: 7 })

process.on('SIGTERM', async () => {
  await sdl.shutdown() // sends the last batch
  process.exit(0)
})
```

In Node, `autocapture: true` means one thing: an **uncaught exception** is sent as a `critical` log line with its stack, then the process exits with code 1, as Node would have. Leave it out to send only your own calls. There are no page views or sessions on a server.

## pino

A second stream turns each pino line into a log line:

```js
import pino from 'pino'
import { Writable } from 'node:stream'

import { sdl } from './simpledatalogs.js'

// pino's numeric levels to simpledatalogs levels.
const levelNames = { 10: 'debug', 20: 'debug', 30: 'info', 40: 'warning', 50: 'error', 60: 'critical' }

const toSimpleDataLogs = new Writable({
  write(chunk, _encoding, callback) {
    const { level, msg, time, pid, hostname, ...fields } = JSON.parse(chunk)
    sdl.log(levelNames[level] ?? 'info', msg ?? '', fields)
    callback()
  },
})

export const logger = pino({}, pino.multistream([{ stream: process.stdout }, { stream: toSimpleDataLogs }]))
```

## winston

```js
import winston from 'winston'
import Transport from 'winston-transport'

import { sdl } from './simpledatalogs.js'

// winston's npm levels to simpledatalogs levels.
const levelNames = { error: 'error', warn: 'warning', info: 'info', http: 'debug', verbose: 'debug', debug: 'debug', silly: 'debug' }

class SimpleDataLogsTransport extends Transport {
  log(info, callback) {
    const { level, message, ...fields } = info
    sdl.log(levelNames[level] ?? 'info', message, fields)
    callback()
  }
}

export const logger = winston.createLogger({ transports: [new winston.transports.Console(), new SimpleDataLogsTransport()] })
```

Both recipes go through the SDK, so lines batch, retry and carry an `event_id`. Fields must be strings, numbers or booleans; nested objects are skipped with one warning.

## In the first minute

* Your `track()` events on the dashboard, and log lines on the **Logs** tab.
* With `autocapture: true`, a crash shows up as a `critical` line with `stack`.
* Lines go out every 10 seconds (5 with `NODE_ENV=development`) or at 50; `await sdl.flush()` sends them now.

## Autocapture defaults (Node)

| Feature | `autocapture: true` |
|---|---|
| Uncaught exceptions | on: logged, flushed for up to 2 s, then exit code 1 |
| Page views, sessions, browser errors | not in Node |
