@bsb/observable-graylog 9.1.3 → 9.6.14
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/LICENSE +660 -660
- package/README.md +2 -2
- package/lib/plugins/observable-graylog/index.d.ts +33 -28
- package/lib/plugins/observable-graylog/index.js +82 -39
- package/lib/schemas/observable-graylog.json +92 -61
- package/lib/schemas/observable-graylog.plugin.json +92 -61
- package/package.json +8 -9
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 "
|
|
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.
|
|
35
|
-
port: av.
|
|
36
|
-
protocol: av.
|
|
34
|
+
host: av.StringSchema;
|
|
35
|
+
port: av.Int32Schema;
|
|
36
|
+
protocol: av.EnumSchema<string[]>;
|
|
37
37
|
httpEndpoint: av.OptionalSchema<av.StringSchema>;
|
|
38
|
-
facility: av.
|
|
38
|
+
facility: av.StringSchema;
|
|
39
39
|
additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
|
|
40
|
-
compress: av.
|
|
40
|
+
compress: av.BoolSchema;
|
|
41
41
|
levels: av.ObjectSchema<{
|
|
42
|
-
debug: av.
|
|
43
|
-
info: av.
|
|
44
|
-
warn: av.
|
|
45
|
-
error: av.
|
|
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.
|
|
51
|
-
port: av.
|
|
52
|
-
protocol: av.
|
|
50
|
+
host: av.StringSchema;
|
|
51
|
+
port: av.Int32Schema;
|
|
52
|
+
protocol: av.EnumSchema<string[]>;
|
|
53
53
|
httpEndpoint: av.OptionalSchema<av.StringSchema>;
|
|
54
|
-
facility: av.
|
|
54
|
+
facility: av.StringSchema;
|
|
55
55
|
additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
|
|
56
|
-
compress: av.
|
|
56
|
+
compress: av.BoolSchema;
|
|
57
57
|
levels: av.ObjectSchema<{
|
|
58
|
-
debug: av.
|
|
59
|
-
info: av.
|
|
60
|
-
warn: av.
|
|
61
|
-
error: av.
|
|
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.
|
|
70
|
-
port: av.
|
|
71
|
-
protocol: av.
|
|
69
|
+
host: av.StringSchema;
|
|
70
|
+
port: av.Int32Schema;
|
|
71
|
+
protocol: av.EnumSchema<string[]>;
|
|
72
72
|
httpEndpoint: av.OptionalSchema<av.StringSchema>;
|
|
73
|
-
facility: av.
|
|
73
|
+
facility: av.StringSchema;
|
|
74
74
|
additionalFields: av.OptionalSchema<av.RecordSchema<av.UnionSchema<[av.StringSchema, av.NumberSchema, av.BoolSchema]>>>;
|
|
75
|
-
compress: av.
|
|
75
|
+
compress: av.BoolSchema;
|
|
76
76
|
levels: av.ObjectSchema<{
|
|
77
|
-
debug: av.
|
|
78
|
-
info: av.
|
|
79
|
-
warn: av.
|
|
80
|
-
error: av.
|
|
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 "
|
|
29
|
-
import
|
|
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.
|
|
35
|
-
port: av.
|
|
36
|
-
protocol: av.
|
|
37
|
-
httpEndpoint: av.optional(av.string().format("url")),
|
|
38
|
-
facility: av.
|
|
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.
|
|
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.
|
|
47
|
-
info: av.
|
|
48
|
-
warn: av.
|
|
49
|
-
error: av.
|
|
50
|
-
}
|
|
51
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "9.6.14",
|
|
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": "
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
"kind": "string",
|
|
13
|
+
"default": "localhost",
|
|
14
|
+
"metadata": {
|
|
15
|
+
"description": "Graylog server hostname"
|
|
16
|
+
}
|
|
17
17
|
},
|
|
18
18
|
"port": {
|
|
19
|
-
"kind": "
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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": "
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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": "
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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": "
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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": "
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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": "
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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": "
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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": "
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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": {}
|