@bsb/observable-graylog 9.1.3 → 9.6.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Graylog (GELF) observable plugin for BSB that sends logs to Graylog servers usin
7
7
  - GELF 1.1 protocol support
8
8
  - Multiple transports: UDP, TCP, and HTTP
9
9
  - Custom fields with automatic trace and span integration
10
- - Optional gzip compression
10
+ - Optional gzip compression for UDP transport
11
11
  - Level filtering to control which logs are sent
12
12
 
13
13
  ## Installation
@@ -51,7 +51,7 @@ plugins:
51
51
  | `httpEndpoint` | HTTP endpoint URL (for HTTP protocol) | - |
52
52
  | `facility` | Facility name for log categorization | `bsb` |
53
53
  | `additionalFields` | Custom fields included in all messages | `{}` |
54
- | `compress` | Enable gzip compression | `true` |
54
+ | `compress` | Enable gzip compression for UDP transport | `true` |
55
55
  | `levels` | Log level filtering | All enabled |
56
56
 
57
57
  ## Usage
@@ -26,39 +26,39 @@
26
26
  */
27
27
  import { BSBObservable, BSBObservableConstructor, BSBError } from "@bsb/base";
28
28
  import { DTrace, LogMeta } from "@bsb/base";
29
- import * as av from "@anyvali/js";
29
+ import * as av from "anyvali";
30
30
  /**
31
31
  * Configuration schema for Graylog observable
32
32
  */
33
33
  export declare const GraylogConfigSchema: av.ObjectSchema<{
34
- host: av.OptionalSchema<av.StringSchema>;
35
- port: av.OptionalSchema<av.Int32Schema>;
36
- protocol: av.OptionalSchema<av.EnumSchema<string[]>>;
34
+ host: av.StringSchema;
35
+ port: av.Int32Schema;
36
+ protocol: av.EnumSchema<string[]>;
37
37
  httpEndpoint: av.OptionalSchema<av.StringSchema>;
38
- facility: av.OptionalSchema<av.StringSchema>;
38
+ facility: av.StringSchema;
39
39
  additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
40
- compress: av.OptionalSchema<av.BoolSchema>;
40
+ compress: av.BoolSchema;
41
41
  levels: av.ObjectSchema<{
42
- debug: av.OptionalSchema<av.BoolSchema>;
43
- info: av.OptionalSchema<av.BoolSchema>;
44
- warn: av.OptionalSchema<av.BoolSchema>;
45
- error: av.OptionalSchema<av.BoolSchema>;
42
+ debug: av.BoolSchema;
43
+ info: av.BoolSchema;
44
+ warn: av.BoolSchema;
45
+ error: av.BoolSchema;
46
46
  }>;
47
47
  }>;
48
48
  export type GraylogConfig = av.Infer<typeof GraylogConfigSchema>;
49
49
  export declare const Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectSchema<{
50
- host: av.OptionalSchema<av.StringSchema>;
51
- port: av.OptionalSchema<av.Int32Schema>;
52
- protocol: av.OptionalSchema<av.EnumSchema<string[]>>;
50
+ host: av.StringSchema;
51
+ port: av.Int32Schema;
52
+ protocol: av.EnumSchema<string[]>;
53
53
  httpEndpoint: av.OptionalSchema<av.StringSchema>;
54
- facility: av.OptionalSchema<av.StringSchema>;
54
+ facility: av.StringSchema;
55
55
  additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
56
- compress: av.OptionalSchema<av.BoolSchema>;
56
+ compress: av.BoolSchema;
57
57
  levels: av.ObjectSchema<{
58
- debug: av.OptionalSchema<av.BoolSchema>;
59
- info: av.OptionalSchema<av.BoolSchema>;
60
- warn: av.OptionalSchema<av.BoolSchema>;
61
- error: av.OptionalSchema<av.BoolSchema>;
58
+ debug: av.BoolSchema;
59
+ info: av.BoolSchema;
60
+ warn: av.BoolSchema;
61
+ error: av.BoolSchema;
62
62
  }>;
63
63
  }>>;
64
64
  /**
@@ -66,22 +66,23 @@ export declare const Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectS
66
66
  */
67
67
  export declare class Plugin extends BSBObservable<InstanceType<typeof Config>> {
68
68
  static Config: import("@bsb/base").BSBPluginConfigClass<av.ObjectSchema<{
69
- host: av.OptionalSchema<av.StringSchema>;
70
- port: av.OptionalSchema<av.Int32Schema>;
71
- protocol: av.OptionalSchema<av.EnumSchema<string[]>>;
69
+ host: av.StringSchema;
70
+ port: av.Int32Schema;
71
+ protocol: av.EnumSchema<string[]>;
72
72
  httpEndpoint: av.OptionalSchema<av.StringSchema>;
73
- facility: av.OptionalSchema<av.StringSchema>;
73
+ facility: av.StringSchema;
74
74
  additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
75
- compress: av.OptionalSchema<av.BoolSchema>;
75
+ compress: av.BoolSchema;
76
76
  levels: av.ObjectSchema<{
77
- debug: av.OptionalSchema<av.BoolSchema>;
78
- info: av.OptionalSchema<av.BoolSchema>;
79
- warn: av.OptionalSchema<av.BoolSchema>;
80
- error: av.OptionalSchema<av.BoolSchema>;
77
+ debug: av.BoolSchema;
78
+ info: av.BoolSchema;
79
+ warn: av.BoolSchema;
80
+ error: av.BoolSchema;
81
81
  }>;
82
82
  }>>;
83
83
  private logFormatter;
84
84
  private isDisposed;
85
+ private defaultFields;
85
86
  constructor(config: BSBObservableConstructor<InstanceType<typeof Config>>);
86
87
  init(): Promise<void>;
87
88
  run(): Promise<void>;
@@ -89,6 +90,10 @@ export declare class Plugin extends BSBObservable<InstanceType<typeof Config>> {
89
90
  * Send log to Graylog
90
91
  */
91
92
  private sendLog;
93
+ private sendGelfMessage;
94
+ private sendUdp;
95
+ private sendTcp;
96
+ private sendHttp;
92
97
  debug(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
93
98
  info(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
94
99
  warn(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
@@ -25,30 +25,35 @@
25
25
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
26
26
  */
27
27
  import { BSBObservable, createConfigSchema, LogFormatter, BSBError } from "@bsb/base";
28
- import * as av from "@anyvali/js";
29
- import * as gelfPro from "gelf-pro";
28
+ import * as av from "anyvali";
29
+ import dgram from "node:dgram";
30
+ import net from "node:net";
31
+ import http from "node:http";
32
+ import https from "node:https";
33
+ import os from "node:os";
34
+ import { gzipSync } from "node:zlib";
30
35
  /**
31
36
  * Configuration schema for Graylog observable
32
37
  */
33
38
  export const GraylogConfigSchema = av.object({
34
- host: av.optional(av.string()).default("localhost"),
35
- port: av.optional(av.int32().min(1).max(65535)).default(12201),
36
- protocol: av.optional(av.enum_(["udp", "tcp", "http"])).default("udp"),
37
- httpEndpoint: av.optional(av.string().format("url")),
38
- facility: av.optional(av.string()).default("bsb"),
39
+ host: av.string().default("localhost").describe("Graylog server hostname"),
40
+ port: av.int32().min(1).max(65535).default(12201).describe("Graylog GELF server port"),
41
+ protocol: av.enum_(["udp", "tcp", "http"]).default("udp").describe("Transport protocol used to send GELF messages"),
42
+ httpEndpoint: av.optional(av.string().format("url")).describe("HTTP GELF endpoint used when protocol is http"),
43
+ facility: av.string().default("bsb").describe("GELF facility field value"),
39
44
  additionalFields: av.optional(av.record(av.union([
40
- av.string(),
41
- av.number(),
42
- av.bool(),
43
- ]))).default({}),
44
- compress: av.optional(av.bool()).default(true),
45
+ av.string().describe("String additional field value"),
46
+ av.number().describe("Numeric additional field value"),
47
+ av.bool().describe("Boolean additional field value"),
48
+ ]))).default({}).describe("Additional static GELF fields added to every log message"),
49
+ compress: av.bool().default(true).describe("Whether UDP GELF message compression is enabled"),
45
50
  levels: av.object({
46
- debug: av.optional(av.bool()).default(true),
47
- info: av.optional(av.bool()).default(true),
48
- warn: av.optional(av.bool()).default(true),
49
- error: av.optional(av.bool()).default(true),
50
- }, { unknownKeys: "strip" }),
51
- }, { unknownKeys: "strip" });
51
+ debug: av.bool().default(true).describe("Whether debug logs are sent to Graylog"),
52
+ info: av.bool().default(true).describe("Whether info logs are sent to Graylog"),
53
+ warn: av.bool().default(true).describe("Whether warning logs are sent to Graylog"),
54
+ error: av.bool().default(true).describe("Whether error logs are sent to Graylog"),
55
+ }).describe("Log level enablement"),
56
+ }).describe("Graylog observable plugin configuration");
52
57
  export const Config = createConfigSchema({
53
58
  name: 'observable-graylog',
54
59
  description: 'Graylog GELF observable plugin for centralized log ingestion',
@@ -82,27 +87,15 @@ export class Plugin extends BSBObservable {
82
87
  static Config = Config;
83
88
  logFormatter = new LogFormatter();
84
89
  isDisposed = false;
90
+ defaultFields = {};
85
91
  constructor(config) {
86
92
  super(config);
87
93
  }
88
94
  async init() {
89
- // Configure gelf-pro adapter
90
- const adapterName = this.config.protocol === "tcp" ? "tcp" : "udp";
91
- const adapterOptions = {
92
- host: this.config.host,
93
- port: this.config.port,
94
- };
95
- // Set default fields including facility
96
- const fields = {
95
+ this.defaultFields = {
97
96
  facility: this.config.facility,
98
97
  ...this.config.additionalFields,
99
98
  };
100
- // Initialize gelf-pro
101
- gelfPro.setConfig({
102
- adapterName,
103
- adapterOptions,
104
- fields,
105
- });
106
99
  }
107
100
  async run() {
108
101
  // No runtime setup needed
@@ -135,17 +128,68 @@ export class Plugin extends BSBObservable {
135
128
  }
136
129
  }
137
130
  }
138
- // Send to Graylog using the message method
139
- gelfPro.message(formattedMessage, gelfLevel, extraFields, (err) => {
140
- if (err) {
141
- console.error(`[observable-graylog] Failed to send log: ${err.message}`);
142
- }
143
- });
131
+ this.sendGelfMessage(formattedMessage, gelfLevel, extraFields);
144
132
  }
145
133
  catch (err) {
146
134
  console.error(`[observable-graylog] Send error: ${err.message}`);
147
135
  }
148
136
  }
137
+ sendGelfMessage(shortMessage, level, extraFields) {
138
+ const payload = JSON.stringify({
139
+ version: "1.1",
140
+ host: os.hostname(),
141
+ short_message: shortMessage,
142
+ timestamp: Date.now() / 1000,
143
+ level,
144
+ ...this.defaultFields,
145
+ ...extraFields,
146
+ });
147
+ if (this.config.protocol === "http") {
148
+ this.sendHttp(payload);
149
+ return;
150
+ }
151
+ if (this.config.protocol === "tcp") {
152
+ this.sendTcp(payload);
153
+ return;
154
+ }
155
+ this.sendUdp(payload);
156
+ }
157
+ sendUdp(payload) {
158
+ const socket = dgram.createSocket("udp4");
159
+ const message = this.config.compress ? gzipSync(Buffer.from(payload)) : Buffer.from(payload);
160
+ socket.send(message, this.config.port, this.config.host, (err) => {
161
+ socket.close();
162
+ if (err) {
163
+ console.error(`[observable-graylog] Failed to send UDP log: ${err.message}`);
164
+ }
165
+ });
166
+ }
167
+ sendTcp(payload) {
168
+ const socket = net.createConnection({ host: this.config.host, port: this.config.port }, () => {
169
+ socket.end(`${payload}\0`);
170
+ });
171
+ socket.on("error", (err) => {
172
+ console.error(`[observable-graylog] Failed to send TCP log: ${err.message}`);
173
+ });
174
+ }
175
+ sendHttp(payload) {
176
+ const endpoint = new URL(this.config.httpEndpoint ?? `http://${this.config.host}:${this.config.port}/gelf`);
177
+ const body = Buffer.from(payload);
178
+ const client = endpoint.protocol === "https:" ? https : http;
179
+ const req = client.request(endpoint, {
180
+ method: "POST",
181
+ headers: {
182
+ "content-type": "application/json",
183
+ "content-length": body.byteLength,
184
+ },
185
+ }, (res) => {
186
+ res.resume();
187
+ });
188
+ req.on("error", (err) => {
189
+ console.error(`[observable-graylog] Failed to send HTTP log: ${err.message}`);
190
+ });
191
+ req.end(body);
192
+ }
149
193
  // Logging methods
150
194
  debug(trace, pluginName, message, meta) {
151
195
  if (this.mode === "production" || !this.config.levels.debug) {
@@ -183,6 +227,5 @@ export class Plugin extends BSBObservable {
183
227
  }
184
228
  dispose() {
185
229
  this.isDisposed = true;
186
- // gelf-pro doesn't require explicit cleanup
187
230
  }
188
231
  }
@@ -1,54 +1,57 @@
1
1
  {
2
2
  "pluginName": "observable-graylog",
3
3
  "events": {},
4
- "version": "9.1.3",
4
+ "version": "9.6.12",
5
5
  "configSchema": {
6
6
  "anyvaliVersion": "1.0",
7
- "schemaVersion": "1",
7
+ "schemaVersion": "1.1",
8
8
  "root": {
9
9
  "kind": "object",
10
10
  "properties": {
11
11
  "host": {
12
- "kind": "optional",
13
- "inner": {
14
- "kind": "string"
15
- },
16
- "default": "localhost"
12
+ "kind": "string",
13
+ "default": "localhost",
14
+ "metadata": {
15
+ "description": "Graylog server hostname"
16
+ }
17
17
  },
18
18
  "port": {
19
- "kind": "optional",
20
- "inner": {
21
- "kind": "int32",
22
- "min": 1,
23
- "max": 65535
24
- },
25
- "default": 12201
19
+ "kind": "int32",
20
+ "min": 1,
21
+ "max": 65535,
22
+ "default": 12201,
23
+ "metadata": {
24
+ "description": "Graylog GELF server port"
25
+ }
26
26
  },
27
27
  "protocol": {
28
- "kind": "optional",
29
- "inner": {
30
- "kind": "enum",
31
- "values": [
32
- "udp",
33
- "tcp",
34
- "http"
35
- ]
36
- },
37
- "default": "udp"
28
+ "kind": "enum",
29
+ "values": [
30
+ "udp",
31
+ "tcp",
32
+ "http"
33
+ ],
34
+ "default": "udp",
35
+ "metadata": {
36
+ "description": "Transport protocol used to send GELF messages"
37
+ }
38
38
  },
39
39
  "httpEndpoint": {
40
40
  "kind": "optional",
41
41
  "inner": {
42
42
  "kind": "string",
43
43
  "format": "url"
44
+ },
45
+ "metadata": {
46
+ "description": "HTTP GELF endpoint used when protocol is http"
44
47
  }
45
48
  },
46
49
  "facility": {
47
- "kind": "optional",
48
- "inner": {
49
- "kind": "string"
50
- },
51
- "default": "bsb"
50
+ "kind": "string",
51
+ "default": "bsb",
52
+ "metadata": {
53
+ "description": "GELF facility field value"
54
+ }
52
55
  },
53
56
  "additionalFields": {
54
57
  "kind": "optional",
@@ -58,66 +61,94 @@
58
61
  "kind": "union",
59
62
  "variants": [
60
63
  {
61
- "kind": "string"
64
+ "kind": "string",
65
+ "metadata": {
66
+ "description": "String additional field value"
67
+ }
62
68
  },
63
69
  {
64
- "kind": "number"
70
+ "kind": "number",
71
+ "metadata": {
72
+ "description": "Numeric additional field value"
73
+ }
65
74
  },
66
75
  {
67
- "kind": "bool"
76
+ "kind": "bool",
77
+ "metadata": {
78
+ "description": "Boolean additional field value"
79
+ }
68
80
  }
69
81
  ]
70
82
  }
71
83
  },
72
- "default": {}
84
+ "default": {},
85
+ "metadata": {
86
+ "description": "Additional static GELF fields added to every log message"
87
+ }
73
88
  },
74
89
  "compress": {
75
- "kind": "optional",
76
- "inner": {
77
- "kind": "bool"
78
- },
79
- "default": true
90
+ "kind": "bool",
91
+ "default": true,
92
+ "metadata": {
93
+ "description": "Whether UDP GELF message compression is enabled"
94
+ }
80
95
  },
81
96
  "levels": {
82
97
  "kind": "object",
83
98
  "properties": {
84
99
  "debug": {
85
- "kind": "optional",
86
- "inner": {
87
- "kind": "bool"
88
- },
89
- "default": true
100
+ "kind": "bool",
101
+ "default": true,
102
+ "metadata": {
103
+ "description": "Whether debug logs are sent to Graylog"
104
+ }
90
105
  },
91
106
  "info": {
92
- "kind": "optional",
93
- "inner": {
94
- "kind": "bool"
95
- },
96
- "default": true
107
+ "kind": "bool",
108
+ "default": true,
109
+ "metadata": {
110
+ "description": "Whether info logs are sent to Graylog"
111
+ }
97
112
  },
98
113
  "warn": {
99
- "kind": "optional",
100
- "inner": {
101
- "kind": "bool"
102
- },
103
- "default": true
114
+ "kind": "bool",
115
+ "default": true,
116
+ "metadata": {
117
+ "description": "Whether warning logs are sent to Graylog"
118
+ }
104
119
  },
105
120
  "error": {
106
- "kind": "optional",
107
- "inner": {
108
- "kind": "bool"
109
- },
110
- "default": true
121
+ "kind": "bool",
122
+ "default": true,
123
+ "metadata": {
124
+ "description": "Whether error logs are sent to Graylog"
125
+ }
111
126
  }
112
127
  },
113
- "required": [],
114
- "unknownKeys": "strip"
128
+ "required": [
129
+ "debug",
130
+ "info",
131
+ "warn",
132
+ "error"
133
+ ],
134
+ "unknownKeys": "strip",
135
+ "metadata": {
136
+ "description": "Log level enablement"
137
+ }
115
138
  }
116
139
  },
117
140
  "required": [
141
+ "host",
142
+ "port",
143
+ "protocol",
144
+ "facility",
145
+ "compress",
118
146
  "levels"
119
147
  ],
120
- "unknownKeys": "strip"
148
+ "unknownKeys": "strip",
149
+ "metadata": {
150
+ "description": "Graylog observable plugin configuration"
151
+ }
121
152
  },
122
153
  "definitions": {},
123
154
  "extensions": {}