Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new documentation: Send logs from Nodejs winston to Signoz cloud #1026

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions constants/docsSideNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,17 @@ const docsSideNav = [
],
},
{
type: 'doc',
route: '/docs/userguide/collecting_nodejs_winston_logs',
label: 'NodeJS Winston logs',
type: 'category',
isExpanded: false,
label: 'Node js',
route: '/docs/logs-management/send-logs/node-js',
items: [
{
type: 'doc',
route: '/docs/userguide/collecting_nodejs_winston_logs',
label: 'Winston',
},
],
},
{
type: 'doc',
Expand Down
17 changes: 17 additions & 0 deletions data/docs/logs-management/send-logs/node-js.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
date: 2024-12-06
id: nodejs-logs
title: Send Nodejs logs to SigNoz
---

There are multiple ways in which you can send nodejs application logs to SigNoz

<DocCardContainer>

<DocCard
title="Winston"
description="Send logs from Node.js Winston without the requiring Otel collectors to Signoz Cloud"
href="/docs/userguide/collecting_nodejs_winston_logs"
/>

</DocCardContainer>
317 changes: 309 additions & 8 deletions data/docs/userguide/collecting_nodejs_winston_logs.mdx
Original file line number Diff line number Diff line change
@@ -1,36 +1,337 @@
---
date: 2024-06-06
title: Collecting NodeJS winston logs
date: 2024-12-06
title: Collecting Nodejs winston logs
id: collecting_nodejs_winston_logs
---


If you are using `winston` as your logging library in your Nodejs application, you can export these logs to SigNoz very easily using various transports provided by `winston`.

<Admonition>
The default logging level can be configured.

To send all the logs to SigNoz please change the default log level to DEBUG.

```
const logger = winston.createLogger({
level: 'debug',
// ... other configurations
});
```

</Admonition>

<Tabs>
<TabItem value="cloud" label="SigNoz Cloud" default>

For sending logs to SigNoz cloud, while running the above example set the below environment variables

- The value of `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable will be `https://ingest.{region}.signoz.cloud:443/v1/logs` where depending on the choice of your region for SigNoz cloud, the otlp endpoint will vary according to this table.

| Region | Endpoint |
| ------ | -------------------------- |
| US | ingest.us.signoz.cloud:443 |
| IN | ingest.in.signoz.cloud:443 |
| EU | ingest.eu.signoz.cloud:443 |

- The value of `SIGNOZ_ACCESS_TOKEN` environment variable will be `<SIGNOZ_INGESTION_KEY>` where `<SIGNOZ_INGESTION_KEY>` is your ingestion key

</TabItem>

<TabItem value="self-host" label="Self-Host" default>
For sending logs to Self hosted Signoz, we need it's running instance. You can follow these steps to [install](https://signoz.io/docs/install/docker/) signoz and set the below environment variables

- The value of `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable will be `https://localhost:4318/v1/logs` and here we don't need `SIGNOZ_ACCESS_TOKEN`

```javascript
const otlpExporter = new OTLPTraceExporter({
url: 'http://localhost:4318/v1/logs',
headers: {
'Content-Type': 'application/json',
},
})
```

Set a proxy in your `package.json` pointing to to the SigNoz OTLP HTTP endpoint. This is an easier temporary setup to bypass CORS issues. You can use [this documentation](https://signoz.io/docs/userguide/otlp-http-enable-cors/) to guide you on dealing with CORS issues for a permanent setup.

```bash
{
"proxy": "http://localhost:4318"
}
```

</TabItem>
</Tabs>

## How to collect logs from Node.js Winston without the requiring Otel collectors

- First we will install a few dependencies using npm

```
npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/winston-transport @opentelemetry/resources winston dotenv
```

- logger.js

```javascript
const logsAPI = require('@opentelemetry/api-logs')
const { LoggerProvider, SimpleLogRecordProcessor } = require('@opentelemetry/sdk-logs')
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http')
const { OpenTelemetryTransportV3 } = require('@opentelemetry/winston-transport')
const { Resource } = require('@opentelemetry/resources')
const winston = require('winston')
require('dotenv').config()

// Initialize the Logger provider
const loggerProvider = new LoggerProvider({
resource: new Resource({
'service.name': 'winston-logger',
'service.version': '1.0.0',
'deployment.environment': process.env.NODE_ENV || 'development',
}),
})

// Configure OTLP exporter for SigNoz
const otlpExporter = new OTLPLogExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
headers: {
'signoz-access-token': process.env.SIGNOZ_ACCESS_TOKEN,
},
})

// Add processor with the OTLP exporter
loggerProvider.addLogRecordProcessor(new SimpleLogRecordProcessor(otlpExporter))

// Set the global logger provider
logsAPI.logs.setGlobalLoggerProvider(loggerProvider)

// Create Winston logger with Console and OpenTelemetry transports
const logger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.metadata(),
winston.format.json()
),
defaultMeta: {
service: 'winston-logger',
environment: process.env.NODE_ENV || 'development',
},
transports: [
new OpenTelemetryTransportV3({
loggerProvider,
logAttributes: {
'service.name': 'winston-logger',
'deployment.environment': process.env.NODE_ENV || 'development',
},
}),
],
})

module.exports = logger
```

- example index.js

```javascript
const express = require('express')
const logger = require('./logger')
const { v4: uuidv4 } = require('uuid')

const PORT = process.env.PORT || 5555
const app = express()

app.use((req, res, next) => {
const requestId = uuidv4()
req.requestId = requestId

logger.info({
message: 'Incoming Request',
method: req.method,
path: req.path,
requestId: requestId,
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
})

req.startTime = Date.now()

res.on('finish', () => {
const duration = Date.now() - req.startTime

logger.info({
message: 'Request Processed',
method: req.method,
path: req.path,
requestId: requestId,
statusCode: res.statusCode,
responseTime: `${duration}ms`,
})
})

next()
})

app.use(express.json())

app.get('/', (req, res) => {
try {
logger.debug({
message: 'Accessing root endpoint',
requestId: req.requestId,
})

res.json({
method: req.method,
message: 'Hello World',
timestamp: new Date().toISOString(),
requestId: req.requestId,
})
} catch (error) {
logger.error({
message: 'Error in root endpoint',
error: error.message,
stack: error.stack,
requestId: req.requestId,
})
res.status(500).json({ error: 'Internal Server Error' })
}
})

app.get('/404', (req, res) => {
logger.warn({
message: '404 Route Accessed',
requestId: req.requestId,
})

res.status(404).json({
error: 'Not Found',
requestId: req.requestId,
})
})

app.get('/user', (req, res) => {
try {
// Simulating an authentication failure
throw new Error('Authentication Failed')
} catch (error) {
logger.error({
message: 'User Authentication Error',
error: error.message,
stack: error.stack,
requestId: req.requestId,
})

res.status(401).json({
error: 'Unauthorized',
requestId: req.requestId,
})
}
})

app.use((err, req, res, next) => {
logger.error({
message: 'Unhandled Error',
error: err.message,
stack: err.stack,
requestId: req.requestId || 'unknown',
})

res.status(500).json({
error: 'Unexpected Server Error',
requestId: req.requestId || 'unknown',
})
})

const server = app.listen(PORT, () => {
logger.info({
message: 'Server Started',
port: PORT,
environment: process.env.NODE_ENV || 'development',
})

console.log(`Listening on http://localhost:${PORT}`)
})

process.on('SIGTERM', () => {
logger.warn('SIGTERM received. Shutting down gracefully.')
server.close(() => {
logger.info('Server closed. Process terminating.')
process.exit(0)
})
})

module.exports = app
```

- Now you can run your application by running `node index.js`
- If there are no errors your logs will be visible on SigNoz UI.

## Collecting Nodejs logs when application is deployed on Docker or Kubernetes

When your application is deployed in Docker or a Kubernetes cluster the logs from the console are automatically collected and stored in the node. The SigNoz collector will automatically collect the logs and it will be visible on the SigNoz UI.
When your application is deployed in Docker or a Kubernetes cluster, the logs from the console are automatically collected and stored in the node. The SigNoz collector will automatically collect the logs and they will be visible on the SigNoz UI.

### Console Transport Configuration

You can add a console transport very easily as stated <a href="https://github.com/winstonjs/winston/blob/master/docs/transports.md#console-transport" rel="noopener noreferrer nofollow" target="_blank">here</a>.
You can easily add a console transport to your Winston logger with the following code:

```javascript
logger.add(new winston.transports.Console(options))
```
logger.add(new winston.transports.Console(options));

The Console transport supports several configuration options:

- **level:** Specifies the level of messages this transport should log (default: level set on parent logger).
- **silent:** A boolean flag to suppress output (default is false).
- **eol:** Defines the end-of-line characters to use (default is `os.EOL`).
- **stderrLevels:** An array of log levels to be sent to stderr instead of stdout. For example: `['error', 'debug', 'info']` (default is an empty array).
- **consoleWarnLevels:** An array of log levels to use `console.warn()` or stderr (in Node.js) instead of stdout. For example: `['warn', 'debug']` (default is an empty array).

Example configuration:

```javascript
logger.add(
new winston.transports.Console({
level: 'info',
format: winston.format.simple(),
silent: false,
stderrLevels: ['error'],
})
)
```

By using these configuration options, you can fine-tune how your logs are output and collected when deployed in containerized environments like Docker or Kubernetes.

## Collecting Nodejs logs when application is deployed on a Host

When you run your application directly on the host, you will be required to add a intermediary medium ex:- a file, where you can export your logs and the otel-collector can read them and push to signoz.

You can add a file transport very easily as stated <a href="https://github.com/winstonjs/winston/blob/master/docs/transports.md#file-transport" rel="noopener noreferrer nofollow" target="_blank">here</a>.

```
logger.add(new winston.transports.File(options));
logger.add(new winston.transports.File({
filename: path.join(process.cwd(), 'logs', 'application.log'),
level: 'debug',
handleExceptions: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
tailable: true,
}));
```

Once you run your application and the logs are added to a file, you can configure otel collector to read from that file.

For configuring it you can follow the guide [here](/docs/userguide/collect_logs_from_file).

Once you configure the otel collector the logs will be visible on the UI.

<figure data-zoomable align="center">
<img
className="box-shadowed-image"
src="/img/docs/logs-management/send-logs/node-winston-logs.webp"
alt="Send Nodejs winston logs to Signoz"
/>
<figcaption>
<i>Send Nodejs winston logs to Signoz</i>
</figcaption>
</figure>
Loading