UNPKG

2.86 kBJavaScriptView Raw
1const Transport = require('winston-transport')
2const Batcher = require('./src/batcher')
3const { MESSAGE } = require('triple-beam')
4
5/**
6 * A Winston transport for Grafana Loki.
7 *
8 * @class LokiTransport
9 * @extends {Transport}
10 */
11class LokiTransport extends Transport {
12 /**
13 * Creates an instance of LokiTransport.
14 * @param {*} options
15 * @memberof LokiTransport
16 */
17 constructor (options) {
18 super(options)
19
20 // Pass all the given options to batcher
21 this.batcher = new Batcher({
22 host: options.host,
23 basicAuth: options.basicAuth,
24 headers: options.headers || {},
25 interval: options.interval,
26 json: options.json,
27 batching: options.batching !== false,
28 clearOnError: options.clearOnError,
29 onConnectionError: options.onConnectionError,
30 replaceTimestamp: options.replaceTimestamp,
31 gracefulShutdown: options.gracefulShutdown !== false,
32 timeout: options.timeout
33 })
34
35 this.useCustomFormat = options.format !== undefined
36 this.labels = options.labels
37 }
38
39 /**
40 * An overwrite of winston-transport's log(),
41 * which the Winston logging library uses
42 * when pushing logs to a transport.
43 *
44 * @param {*} info
45 * @param {*} callback
46 * @memberof LokiTransport
47 */
48 log (info, callback) {
49 // Immediately tell Winston that this transport has received the log.
50 setImmediate(() => {
51 this.emit('logged', info)
52 })
53
54 // Deconstruct the log
55 const { label, labels, timestamp, level, message, ...rest } = info
56
57 // build custom labels if provided
58 let lokiLabels = { level: level }
59
60 if (this.labels) {
61 lokiLabels = Object.assign(lokiLabels, this.labels)
62 } else {
63 lokiLabels.job = label
64 }
65
66 lokiLabels = Object.assign(lokiLabels, labels)
67
68 // follow the format provided
69 const line = this.useCustomFormat
70 ? info[MESSAGE]
71 : `${message} ${
72 rest && Object.keys(rest).length > 0 ? JSON.stringify(rest) : ''
73 }`
74
75 // Make sure all label values are strings
76 lokiLabels = Object.fromEntries(Object.entries(lokiLabels).map(([key, value]) => [key, value ? value.toString() : value]))
77
78 // Construct the log to fit Grafana Loki's accepted format
79 let ts
80 if (timestamp) {
81 ts = new Date(timestamp)
82 ts = isNaN(ts) ? Date.now() : ts.valueOf()
83 } else {
84 ts = Date.now()
85 }
86
87 const logEntry = {
88 labels: lokiLabels,
89 entries: [
90 {
91 ts,
92 line
93 }
94 ]
95 }
96
97 // Pushes the log to the batcher
98 this.batcher.pushLogEntry(logEntry).catch(err => {
99 // eslint-disable-next-line no-console
100 console.error(err)
101 })
102
103 // Trigger the optional callback
104 callback()
105 }
106
107 /**
108 * Send batch to loki when clean up
109 */
110 close () {
111 this.batcher.close()
112 }
113}
114
115module.exports = LokiTransport