@theotherwillembotha/node-red-loki 0.0.55
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 +15 -0
- package/README.md +72 -0
- package/build/GenerateNodes.js +8 -0
- package/build/Nodes.html +153 -0
- package/build/Nodes.js +18 -0
- package/build/Plugins.js +70 -0
- package/build/index.js +17 -0
- package/build/loki/node/LokiLoggerConfigNode.js +100 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Willem Botha
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @theotherwillembotha/node-red-loki
|
|
2
|
+
|
|
3
|
+
Grafana Loki logger config node for Node-RED. Extends the logging infrastructure provided by [@theotherwillembotha/node-red-plugincore](https://github.com/theotherwillembotha/nodered_plugincore) with a Loki transport, so any node built with the `@Logger` decorator can ship structured log entries directly to a Loki instance.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Usage in Node-RED
|
|
8
|
+
|
|
9
|
+
### Installation
|
|
10
|
+
|
|
11
|
+
Either use the **Manage Palette** option in the Node-RED editor, or run the following command in your Node-RED user directory (typically `~/.node-red`):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @theotherwillembotha/node-red-loki
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
> [!IMPORTANT]
|
|
18
|
+
> **This plugin requires [`@theotherwillembotha/node-red-plugincore`](https://github.com/theotherwillembotha/nodered_plugincore) to be installed.**
|
|
19
|
+
>
|
|
20
|
+
> `node-red-plugincore` is declared as a dependency and npm will install it automatically alongside this package. However, due to a [known Node-RED limitation](https://github.com/node-red/node-red/issues/3529), packages that arrive as transitive npm dependencies are only discovered by the Node-RED runtime on the **next startup**.
|
|
21
|
+
>
|
|
22
|
+
> **You have two options:**
|
|
23
|
+
> - Install [`@theotherwillembotha/node-red-plugincore`](https://flows.nodered.org/node/@theotherwillembotha/node-red-plugincore) via the palette manager or `npm install` **first**, then install this plugin — both will be available immediately without a restart.
|
|
24
|
+
> - Install this plugin directly — `node-red-plugincore` will be installed automatically alongside it. **Restart Node-RED** once and both packages will be fully loaded.
|
|
25
|
+
|
|
26
|
+
### Config node
|
|
27
|
+
|
|
28
|
+
This package adds a single config node: **Loki Logger**.
|
|
29
|
+
|
|
30
|
+
The Loki Logger config node appears in the logger selector of any node that uses the `@Logger` decorator (e.g. the Logger Node from [node-red-telemetry](https://github.com/theotherwillembotha/nodered_telemetry), or any custom node built on this framework).
|
|
31
|
+
|
|
32
|
+
#### Configuration fields
|
|
33
|
+
|
|
34
|
+
| Field | Description |
|
|
35
|
+
|-------|-------------|
|
|
36
|
+
| **Name** | Optional label for this config node |
|
|
37
|
+
| **Host** | Hostname or IP address of your Loki instance (e.g. `loki` or `192.168.1.10`) |
|
|
38
|
+
| **Port** | Loki HTTP port (default: `3100`) |
|
|
39
|
+
| **Tenant ID** | Optional. Set the `X-Scope-OrgID` header for multi-tenant Loki deployments. Leave blank for single-tenant mode. |
|
|
40
|
+
| **Level** | Minimum log level to ship: Debug, Info, Warning, or Error |
|
|
41
|
+
| **Template** | Handlebars template that shapes each log entry. The default `message:{{msg}}` passes the raw message through. Customise to include only the fields you need (e.g. `{{msg.topic}}: {{msg.payload}}`). |
|
|
42
|
+
|
|
43
|
+
#### Example
|
|
44
|
+
|
|
45
|
+
A typical setup for a single-tenant Loki instance running on the same Docker network:
|
|
46
|
+
|
|
47
|
+
- **Host:** `loki`
|
|
48
|
+
- **Port:** `3100`
|
|
49
|
+
- **Tenant ID:** _(blank)_
|
|
50
|
+
- **Level:** `Info`
|
|
51
|
+
- **Template:** `message:{{msg}}`
|
|
52
|
+
|
|
53
|
+
For multi-tenant deployments, set the **Tenant ID** to match your Loki organisation ID. The value is sent as the `X-Scope-OrgID` HTTP header on every push request.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Prerequisites
|
|
58
|
+
|
|
59
|
+
- Node.js 18+
|
|
60
|
+
- Node-RED 3+
|
|
61
|
+
- A running [Grafana Loki](https://grafana.com/oss/loki/) instance reachable from your Node-RED host
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Repository
|
|
66
|
+
|
|
67
|
+
- Source: [github.com/theotherwillembotha/nodered_loki](https://github.com/theotherwillembotha/nodered_loki)
|
|
68
|
+
- Issues: [github.com/theotherwillembotha/nodered_loki/issues](https://github.com/theotherwillembotha/nodered_loki/issues)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
[ISC](LICENSE)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
|
|
4
|
+
const LokiLoggerConfigNode_1 = require("./loki/node/LokiLoggerConfigNode");
|
|
5
|
+
new node_red_plugincore_1.NodeGenerator("./src/loki/")
|
|
6
|
+
.registerNode(LokiLoggerConfigNode_1.LokiLoggerConfigNode)
|
|
7
|
+
.generate("./build/Nodes", "./build/Plugins");
|
|
8
|
+
process.exit(0);
|
package/build/Nodes.html
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
<!--
|
|
3
|
+
|
|
4
|
+
This file was automatically generated using the NODERED Core utility.
|
|
5
|
+
Any modifications to his file will be overwritten the next time the code is regenerated.
|
|
6
|
+
|
|
7
|
+
You have been warned.
|
|
8
|
+
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<script type="text/javascript">
|
|
12
|
+
RED.nodes.registerType('LokiLoggerConfigNode', {
|
|
13
|
+
category: 'config',
|
|
14
|
+
label: function() {
|
|
15
|
+
return this.name
|
|
16
|
+
},
|
|
17
|
+
paletteLabel: 'Loki Logger Config Node',
|
|
18
|
+
defaults: {
|
|
19
|
+
name: {
|
|
20
|
+
value: 'Logger Example',
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
template: {
|
|
24
|
+
value: 'message:{{msg}}',
|
|
25
|
+
required: true
|
|
26
|
+
},
|
|
27
|
+
level: {
|
|
28
|
+
value: 'info',
|
|
29
|
+
required: true
|
|
30
|
+
},
|
|
31
|
+
loki_host: {},
|
|
32
|
+
loki_tenantid: {},
|
|
33
|
+
loki_userid: {},
|
|
34
|
+
loki_authtoken: {},
|
|
35
|
+
},
|
|
36
|
+
oneditprepare: function() {
|
|
37
|
+
// **** LokiLoggerConfigNode **** //
|
|
38
|
+
{
|
|
39
|
+
let node = this;
|
|
40
|
+
let levelSelection = $("#node-config-input-level");
|
|
41
|
+
levelSelection.typedInput({
|
|
42
|
+
type: "level",
|
|
43
|
+
types: [{
|
|
44
|
+
value: "level",
|
|
45
|
+
options: [{
|
|
46
|
+
value: "DEBUG",
|
|
47
|
+
label: "debug"
|
|
48
|
+
}, {
|
|
49
|
+
value: "INFO",
|
|
50
|
+
label: "info"
|
|
51
|
+
}, {
|
|
52
|
+
value: "WARNING",
|
|
53
|
+
label: "warning"
|
|
54
|
+
}, {
|
|
55
|
+
value: "ERROR",
|
|
56
|
+
label: "error"
|
|
57
|
+
}, ]
|
|
58
|
+
}]
|
|
59
|
+
});
|
|
60
|
+
this.editor = RED.editor.createEditor({
|
|
61
|
+
id: 'node-config-input-templateEditor',
|
|
62
|
+
mode: 'ace/mode/handlebars',
|
|
63
|
+
value: this.template
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
oneditsave: function() {
|
|
68
|
+
// **** LokiLoggerConfigNode **** //
|
|
69
|
+
{
|
|
70
|
+
let node = this;
|
|
71
|
+
this.template = this.editor.getValue();
|
|
72
|
+
this.editor.destroy();
|
|
73
|
+
delete this.editor;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
oneditcancel: function() {},
|
|
77
|
+
oneditdelete: function() {},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
<script type="text/html" data-template-name='LokiLoggerConfigNode'>
|
|
84
|
+
<div id='section_LokiLoggerConfigNode'>
|
|
85
|
+
<div id="LoggerConfig">
|
|
86
|
+
<div class="form-row">
|
|
87
|
+
<label class="towb_editorlabel" for="node-config-input-name"><i class="fa fa-font"></i> Name</label>
|
|
88
|
+
<input type="text" id="node-config-input-name" />
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="form-row">
|
|
92
|
+
<label class="towb_editorlabel" for="node-config-input-level"><i class="fa fa-signal"></i> Level</label>
|
|
93
|
+
<input type="text" id="node-config-input-level" />
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="form-row">
|
|
97
|
+
<label class="towb_editorlabel" for="node-config-input-loki_host"><i class="fa fa-server"></i> Host</label>
|
|
98
|
+
<input class="required" type="text" id="node-config-input-loki_host" />
|
|
99
|
+
</div>
|
|
100
|
+
<div class="form-row">
|
|
101
|
+
<label class="towb_editorlabel" for="node-config-input-loki_tenantid"><i class="fa fa-building-o"></i> Tenant ID</label>
|
|
102
|
+
<input class="" type="text" id="node-config-input-loki_tenantid" />
|
|
103
|
+
</div>
|
|
104
|
+
<div class="form-row">
|
|
105
|
+
<label class="towb_editorlabel" for="node-config-input-loki_userid"><i class="fa fa-user"></i> Username</label>
|
|
106
|
+
<input class="" type="text" id="node-config-input-loki_userid" />
|
|
107
|
+
</div>
|
|
108
|
+
<div class="form-row">
|
|
109
|
+
<label class="towb_editorlabel" for="node-config-input-loki_authtoken"><i class="fa fa-key"></i> Auth Token</label>
|
|
110
|
+
<input class="" type="password" id="node-config-input-loki_authtoken" />
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="form-row">
|
|
114
|
+
<label class="towb_editorlabel" for="node-config-input-templateEditor"><i class="fa fa-code"></i> Template</label>
|
|
115
|
+
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-config-input-templateEditor"></div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
</div>
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<script type="text/markdown" data-help-name='LokiLoggerConfigNode'>
|
|
123
|
+
Pushes log messages to a Grafana Loki instance using the Loki HTTP API.
|
|
124
|
+
|
|
125
|
+
### Properties
|
|
126
|
+
: *name* (string) : A descriptive name for this logger configuration.
|
|
127
|
+
: *level* (level) : The minimum log level. Messages below this threshold are suppressed. Options: DEBUG, INFO, WARNING, ERROR.
|
|
128
|
+
: *host* (string) : The base URL of the Loki instance, including port (e.g. `http://localhost:3100`).
|
|
129
|
+
: *tenant ID* (string) : The tenant identifier for multi-tenant Loki deployments (`X-Scope-OrgID` header). Leave blank for single-tenant deployments.
|
|
130
|
+
: *username* (string) : Username for Loki authentication.
|
|
131
|
+
: *auth token* (string) : Authentication token or password for Loki.
|
|
132
|
+
: *template* (mustache) : A Mustache/Handlebars template that formats the log message body.
|
|
133
|
+
|
|
134
|
+
### Mustache Templating
|
|
135
|
+
Mustache/Handlebars templates build the message string pushed to Loki.
|
|
136
|
+
|
|
137
|
+
#### Provided Properties
|
|
138
|
+
: *msg* (object) : The Node-RED message object passed through the node.
|
|
139
|
+
: *currentTime* (string) : The current time in ISO 8601 format.
|
|
140
|
+
|
|
141
|
+
#### Helpers
|
|
142
|
+
: *json* (helper) : Serialises an object to a JSON string using `JSON.stringify`.
|
|
143
|
+
|
|
144
|
+
#### Examples
|
|
145
|
+
- `{{{json msg}}}` — serialises the full message object as JSON.
|
|
146
|
+
|
|
147
|
+
#### References
|
|
148
|
+
- [Grafana Loki — HTTP API](https://grafana.com/docs/loki/latest/reference/loki-http-api/)
|
|
149
|
+
- [Mustache — Wikipedia](https://en.wikipedia.org/wiki/Mustache_(template_system))
|
|
150
|
+
- [Mustache — Playground](https://jgonggrijp.gitlab.io/wontache/playground.html)
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
|
package/build/Nodes.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
|
|
4
|
+
This file was automatically generated using the NODERED Core utility.
|
|
5
|
+
Any modifications to his file will be overwritten the next time the code is regenerated.
|
|
6
|
+
|
|
7
|
+
You have been warned.
|
|
8
|
+
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
const NodeManager = require("@theotherwillembotha/node-red-plugincore").NodeManager;
|
|
12
|
+
const LokiLoggerConfigNode = require("@theotherwillembotha/node-red-loki").LokiLoggerConfigNode;
|
|
13
|
+
module.exports = (RED) => {
|
|
14
|
+
let manager = new NodeManager(RED);
|
|
15
|
+
manager.registerNodeType("LokiLoggerConfigNode", LokiLoggerConfigNode);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
package/build/Plugins.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
|
|
4
|
+
This file was automatically generated using the NODERED Core utility.
|
|
5
|
+
Any modifications to his file will be overwritten the next time the code is regenerated.
|
|
6
|
+
|
|
7
|
+
You have been warned.
|
|
8
|
+
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
const runtime = require("node-red").runtime;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
module.exports = function (RED) {
|
|
15
|
+
|
|
16
|
+
// 1. make a list of all the plugins that need to be installed.
|
|
17
|
+
let pluginList = []
|
|
18
|
+
.map(service => service.getServiceDescriptor())
|
|
19
|
+
.filter(plugin => !RED.plugins.get(plugin.id()));
|
|
20
|
+
|
|
21
|
+
// 2. register a listener if there are any plugins in the pluginList.
|
|
22
|
+
if(pluginList.length > 0){
|
|
23
|
+
RED.events.on('registry:plugin-added', async pluginID => {
|
|
24
|
+
|
|
25
|
+
let addedPlugin = pluginList.find(plugin => plugin.id() === pluginID);
|
|
26
|
+
let plugin = RED.plugins.get(pluginID);
|
|
27
|
+
|
|
28
|
+
if(addedPlugin && plugin.instantiate && !plugin.instance){
|
|
29
|
+
console.log("Deploying instance of: " + plugin.id);
|
|
30
|
+
plugin.instance = new plugin.class();
|
|
31
|
+
|
|
32
|
+
// instantiate the plugin.
|
|
33
|
+
await plugin.instance.init(RED);
|
|
34
|
+
|
|
35
|
+
// publish an event that it has ben deployed.
|
|
36
|
+
RED.events.emit("plugin.instantiated", pluginID);
|
|
37
|
+
|
|
38
|
+
pluginList.splice(pluginList.findIndex(current => current.id() === addedPlugin.id()), 1);
|
|
39
|
+
|
|
40
|
+
// register a flow deployment listener.
|
|
41
|
+
let startupDeployment = true;
|
|
42
|
+
RED.events.on('runtime-event', async (event) => {
|
|
43
|
+
// startup deployment.
|
|
44
|
+
if ("runtime-deploy" === event?.id && startupDeployment) {
|
|
45
|
+
//console.log("STARTUP DEPLOYMENT");
|
|
46
|
+
startupDeployment = false;
|
|
47
|
+
const flows = await runtime.flows.getFlows({});
|
|
48
|
+
await plugin.instance.onDeploy(flows);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// stopping flows on redeployment.
|
|
52
|
+
if ("runtime-state" === event?.id && event?.payload?.state === "stop" && event?.payload?.deploy) {
|
|
53
|
+
//console.log("REDEPLOY!");
|
|
54
|
+
const flows = await runtime.flows.getFlows({});
|
|
55
|
+
await plugin.instance.onDeploy(flows);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. register the plugins.
|
|
63
|
+
[...pluginList].forEach(plugin => {
|
|
64
|
+
RED.plugins.registerPlugin(plugin.id(), {
|
|
65
|
+
type: plugin.type(),
|
|
66
|
+
instantiate:true,
|
|
67
|
+
class: plugin.clazz()
|
|
68
|
+
});
|
|
69
|
+
})
|
|
70
|
+
};
|
package/build/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./loki/node/LokiLoggerConfigNode"), exports);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.LokiLoggerConfigNode = void 0;
|
|
16
|
+
const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
|
|
17
|
+
const node_red_plugincore_2 = require("@theotherwillembotha/node-red-plugincore");
|
|
18
|
+
const winston_1 = require("winston");
|
|
19
|
+
const winston_loki_1 = __importDefault(require("winston-loki"));
|
|
20
|
+
let LokiLoggerConfigNode = class LokiLoggerConfigNode extends node_red_plugincore_1.LoggerConfigNode {
|
|
21
|
+
constructor(node, config) {
|
|
22
|
+
super(node, config);
|
|
23
|
+
const loggerConfig = {
|
|
24
|
+
id: this.id(),
|
|
25
|
+
type: "LOKI",
|
|
26
|
+
template: config.template,
|
|
27
|
+
level: config.level,
|
|
28
|
+
host: config.loki_host,
|
|
29
|
+
userid: config.loki_userid,
|
|
30
|
+
authtoken: config.loki_authtoken,
|
|
31
|
+
tenantid: config.loki_tenantid,
|
|
32
|
+
};
|
|
33
|
+
this._logger = new LokiLogger(loggerConfig);
|
|
34
|
+
}
|
|
35
|
+
logger() {
|
|
36
|
+
return this._logger;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.LokiLoggerConfigNode = LokiLoggerConfigNode;
|
|
40
|
+
exports.LokiLoggerConfigNode = LokiLoggerConfigNode = __decorate([
|
|
41
|
+
(0, node_red_plugincore_1.NodeDescription)({
|
|
42
|
+
id: "LokiLoggerConfigNode",
|
|
43
|
+
name: "Loki Logger",
|
|
44
|
+
group: "config",
|
|
45
|
+
sourceFile: node_red_plugincore_2.SourceUtility.getSourcePath("/build/", "/src/") + "LokiLoggerConfigNode.html",
|
|
46
|
+
package: "@theotherwillembotha/node-red-loki",
|
|
47
|
+
dependencies: [node_red_plugincore_1.LoggerService],
|
|
48
|
+
tags: ["LoggerType"]
|
|
49
|
+
}),
|
|
50
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
51
|
+
], LokiLoggerConfigNode);
|
|
52
|
+
function toWinstonLevel(level) {
|
|
53
|
+
const l = level.toLowerCase();
|
|
54
|
+
return l === 'warning' ? 'warn' : l;
|
|
55
|
+
}
|
|
56
|
+
class LokiLogger extends node_red_plugincore_1.AbstractLogger {
|
|
57
|
+
constructor(config) {
|
|
58
|
+
super(config);
|
|
59
|
+
this.winstonLevel = toWinstonLevel(config.level);
|
|
60
|
+
const transportConfig = {
|
|
61
|
+
headers: {},
|
|
62
|
+
host: config.host,
|
|
63
|
+
json: true,
|
|
64
|
+
format: winston_1.format.json(),
|
|
65
|
+
replaceTimestamp: true,
|
|
66
|
+
onConnectionError: (err) => console.error(`[LokiLogger] connection error (host: ${config.host}):`, err),
|
|
67
|
+
basicAuth: (config.userid && config.authtoken)
|
|
68
|
+
? `${config.userid}:${config.authtoken}`
|
|
69
|
+
: undefined
|
|
70
|
+
};
|
|
71
|
+
if (config.tenantid) {
|
|
72
|
+
transportConfig.headers["X-Scope-OrgID"] = config.tenantid;
|
|
73
|
+
}
|
|
74
|
+
this.winston = (0, winston_1.createLogger)({
|
|
75
|
+
level: this.winstonLevel,
|
|
76
|
+
format: winston_1.format.json(),
|
|
77
|
+
defaultMeta: {},
|
|
78
|
+
transports: [
|
|
79
|
+
new winston_loki_1.default(transportConfig),
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
createLogger(config) {
|
|
84
|
+
return new LokiAppender(config, this.winston, this.winstonLevel);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
class LokiAppender extends node_red_plugincore_1.Log {
|
|
88
|
+
constructor(config, winston, level) {
|
|
89
|
+
super(config);
|
|
90
|
+
this.winston = winston;
|
|
91
|
+
this.level = level;
|
|
92
|
+
}
|
|
93
|
+
writeToLog(_level, message, tags) {
|
|
94
|
+
this.winston.log({
|
|
95
|
+
level: this.level.toLowerCase(),
|
|
96
|
+
message: message,
|
|
97
|
+
labels: tags
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theotherwillembotha/node-red-loki",
|
|
3
|
+
"version": "0.0.55",
|
|
4
|
+
"description": "Grafana Loki logger config node for Node-RED. Built on @theotherwillembotha/node-red-plugincore.",
|
|
5
|
+
"main": "./build/index.js",
|
|
6
|
+
"exports": "./build/index.js",
|
|
7
|
+
"author": "Willem Botha (@theotherwillembotha)",
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"node-red",
|
|
11
|
+
"loki",
|
|
12
|
+
"logging",
|
|
13
|
+
"grafana",
|
|
14
|
+
"observability"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
18
|
+
"build": "npm run clean && tsc && npm run generate-nodes",
|
|
19
|
+
"clean": "rm -rf ./build",
|
|
20
|
+
"generate-nodes": "node build/GenerateNodes.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"build/",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/theotherwillembotha/nodered_loki.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/theotherwillembotha/nodered_loki/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/theotherwillembotha/nodered_loki#readme",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18",
|
|
37
|
+
"nodered": ">=4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@theotherwillembotha/node-red-plugincore": "^0.0.55",
|
|
41
|
+
"winston": "^3.17.0",
|
|
42
|
+
"winston-loki": "^6.1.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@theotherwillembotha/node-red-plugincore": "../nodered_plugincore",
|
|
46
|
+
"@types/node": "^22.15.29",
|
|
47
|
+
"@types/node-red": "~1.3.5"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"node-red": {
|
|
53
|
+
"version": ">=4.0.0",
|
|
54
|
+
"nodes": {
|
|
55
|
+
"loki": "./build/Nodes.js"
|
|
56
|
+
},
|
|
57
|
+
"plugins": {
|
|
58
|
+
"loki": "./build/Plugins.js"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|