diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f9abc7c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Run Manzan", + "request": "launch", + "mainClass": "com.github.theprez.manzan.ManzanMainApp", + "projectName": "manzan", + "args": "--configdir=${workspaceFolder}/config" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c0075f..7286ef3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -103,5 +103,6 @@ "compare": "cpp", "concepts": "cpp", "algorithm": "cpp" - } + }, + "java.configuration.updateBuildConfiguration": "automatic" } \ No newline at end of file diff --git a/camel/pom.xml b/camel/pom.xml index 8ab90eb..2a8a7be 100644 --- a/camel/pom.xml +++ b/camel/pom.xml @@ -154,7 +154,11 @@ fluent-logger 0.3.4 - + + io.github.mjfryc + mjaron-tinyloki-java + 0.3.11 + diff --git a/camel/src/main/java/com/github/theprez/manzan/configuration/DestinationConfig.java b/camel/src/main/java/com/github/theprez/manzan/configuration/DestinationConfig.java index 2433564..1302131 100644 --- a/camel/src/main/java/com/github/theprez/manzan/configuration/DestinationConfig.java +++ b/camel/src/main/java/com/github/theprez/manzan/configuration/DestinationConfig.java @@ -18,6 +18,7 @@ import com.github.theprez.manzan.routes.dest.EmailDestination; import com.github.theprez.manzan.routes.dest.FileDestination; import com.github.theprez.manzan.routes.dest.FluentDDestination; +import com.github.theprez.manzan.routes.dest.GrafanaLokiDestination; import com.github.theprez.manzan.routes.dest.HttpDestination; import com.github.theprez.manzan.routes.dest.KafkaDestination; import com.github.theprez.manzan.routes.dest.SentryDestination; @@ -83,6 +84,12 @@ public synchronized Map getRoutes(CamelContext context) { final String host = getRequiredString(name, "host"); final int port = getRequiredInt(name, "port"); ret.put(name, new FluentDDestination(name, tag, host, port)); + } + case "loki": { + final String url = getRequiredString(name, "url"); + final String username = getRequiredString(name, "username"); + final String password = getRequiredString(name, "password"); + ret.put(name, new GrafanaLokiDestination(name, url, username, password, format)); } break; case "smtp": diff --git a/camel/src/main/java/com/github/theprez/manzan/routes/ManzanRoute.java b/camel/src/main/java/com/github/theprez/manzan/routes/ManzanRoute.java index de3247d..f3c92cd 100644 --- a/camel/src/main/java/com/github/theprez/manzan/routes/ManzanRoute.java +++ b/camel/src/main/java/com/github/theprez/manzan/routes/ManzanRoute.java @@ -13,8 +13,23 @@ public abstract class ManzanRoute extends RouteBuilder { protected static final String EVENT_TYPE = "event_type"; + protected static final String SESSION_ID = "SESSION_ID"; + protected static final String MSG_MESSAGE_ID = "MESSAGE_ID"; + protected static final String MSG_MESSAGE_TYPE = "MESSAGE_TYPE"; + protected static final String MSG_SEVERITY = "SEVERITY"; + protected static final String JOB = "JOB"; + protected static final String MSG_SENDING_USRPRF = "SENDING_USRPRF"; + protected static final String MSG_SENDING_PROGRAM_NAME = "SENDING_PROGRAM_NAME"; + protected static final String MSG_SENDING_MODULE_NAME = "SENDING_MODULE_NAME"; + protected static final String MSG_SENDING_PROCEDURE_NAME = "SENDING_PROCEDURE_NAME"; + protected static final String MSG_ORDINAL_POSITION = "ORDINAL_POSITION"; + protected static final String MSG_MESSAGE_TIMESTAMP = "MESSAGE_TIMESTAMP"; + protected static final String MSG_MESSAGE = "MESSAGE"; + + protected static final int SEVERITY_LIMIT = 29; + protected static String getWatchName(final Exchange exchange) { - final Object watchNameObject = exchange.getIn().getHeader("session_id"); + final Object watchNameObject = exchange.getIn().getHeader(SESSION_ID); if (null == watchNameObject) { throw new RuntimeException("Couldn't figure out watch ID"); } @@ -45,6 +60,10 @@ protected Map getDataMap(final Exchange _exchange) { return (Map) _exchange.getIn().getHeader("data_map"); } + protected T getBody(final Exchange _exchange, Class type) { + return _exchange.getIn().getBody(type); + } + protected String getInUri() { return "direct:" + m_name; } diff --git a/camel/src/main/java/com/github/theprez/manzan/routes/dest/GrafanaLokiDestination.java b/camel/src/main/java/com/github/theprez/manzan/routes/dest/GrafanaLokiDestination.java new file mode 100644 index 0000000..5a487f6 --- /dev/null +++ b/camel/src/main/java/com/github/theprez/manzan/routes/dest/GrafanaLokiDestination.java @@ -0,0 +1,82 @@ +package com.github.theprez.manzan.routes.dest; + +import java.sql.Timestamp; + +import com.github.theprez.manzan.ManzanEventType; +import com.github.theprez.manzan.routes.ManzanRoute; + +import pl.mjaron.tinyloki.ILogStream; +import pl.mjaron.tinyloki.Labels; +import pl.mjaron.tinyloki.LogController; +import pl.mjaron.tinyloki.StreamBuilder; +import pl.mjaron.tinyloki.TinyLoki; + +public class GrafanaLokiDestination extends ManzanRoute { + private final LogController logController; + private final static String endpoint = "/loki/api/v1/push"; + private final static String appLabelName = "app"; + private final static String appLabelValue = "manzan"; + + public GrafanaLokiDestination(final String _name, final String _url, final String _username, final String _password, + final String _format) { + super(_name); + + logController = TinyLoki + .withUrl(_url + endpoint) + .withBasicAuth(_username, _password) + .start(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + logController + .softStop() + .hardStop(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + }); + } + + //@formatter:off + @Override + public void configure() { + from(getInUri()) + .routeId(m_name).process(exchange -> { + final ManzanEventType type = (ManzanEventType) exchange.getIn().getHeader(EVENT_TYPE); + if(ManzanEventType.WATCH_MSG == type) { + StreamBuilder builder = logController + .stream() + .l(appLabelName, appLabelValue) + .l(Labels.LEVEL, ((Integer) get(exchange, MSG_SEVERITY)) > SEVERITY_LIMIT ? Labels.FATAL : Labels.INFO) + .l(SESSION_ID, getWatchName(exchange)); + + String[] keys = { + MSG_MESSAGE_ID, + MSG_MESSAGE_TYPE, + MSG_SEVERITY, + JOB, + MSG_SENDING_USRPRF, + MSG_SENDING_PROGRAM_NAME, + MSG_SENDING_MODULE_NAME, + MSG_SENDING_PROCEDURE_NAME + }; + + for (String key: keys) { + String value = getString(exchange, key); + if(!value.equals("")) { + builder.l(key, value); + } + } + + ILogStream stream = builder.build(); + stream.log(Timestamp.valueOf(getString(exchange, MSG_MESSAGE_TIMESTAMP)).getTime(), getBody(exchange, String.class)); + } else { + throw new RuntimeException("Grafana Loki route doesn't know how to process type "+type); + } + }); + } + //@formatter:on +} diff --git a/camel/src/main/java/com/github/theprez/manzan/routes/dest/SentryDestination.java b/camel/src/main/java/com/github/theprez/manzan/routes/dest/SentryDestination.java index 71cee6a..5c697b9 100644 --- a/camel/src/main/java/com/github/theprez/manzan/routes/dest/SentryDestination.java +++ b/camel/src/main/java/com/github/theprez/manzan/routes/dest/SentryDestination.java @@ -16,16 +16,6 @@ public class SentryDestination extends ManzanRoute { private static SentryDestination m_singleton = null; - private static final String MSG_MESSAGE = "MESSAGE"; - private static final String MSG_MESSAGE_ID = "MESSAGE_ID"; - private static final String MSG_ORDINAL_POSITION = "ORDINAL_POSITION"; - private static final String MSG_SENDING_MODULE_NAME = "SENDING_MODULE_NAME"; - - private static final String MSG_SENDING_PROCEDURE_NAME = "SENDING_PROCEDURE_NAME"; - private static final String MSG_SENDING_PROGRAM_NAME = "SENDING_PROGRAM_NAME"; - private static final String MSG_SENDING_USRPRF = "SENDING_USRPRF"; - - private static final String MSG_SEVERITY = "SEVERITY"; public SentryDestination(final String _name, final String _dsn) { super(_name); @@ -68,7 +58,7 @@ public void configure() { SentryLevel level; final int sev = (Integer) get(exchange, MSG_SEVERITY); - if (sev > 29) { + if (sev > SEVERITY_LIMIT) { level = SentryLevel.ERROR; } else { level = SentryLevel.INFO; diff --git a/camel/src/main/java/com/github/theprez/manzan/routes/dest/TwilioDestination.java b/camel/src/main/java/com/github/theprez/manzan/routes/dest/TwilioDestination.java index cb57722..cdea30c 100644 --- a/camel/src/main/java/com/github/theprez/manzan/routes/dest/TwilioDestination.java +++ b/camel/src/main/java/com/github/theprez/manzan/routes/dest/TwilioDestination.java @@ -19,6 +19,6 @@ public TwilioDestination(CamelContext context, final String _name, final String @Override protected void customPostProcess(Exchange exchange) { - exchange.getIn().setHeader("CamelTwilio.body", exchange.getIn().getBody(String.class)); + exchange.getIn().setHeader("CamelTwilio.body", getBody(exchange, String.class)); } } diff --git a/camel/src/main/java/com/github/theprez/manzan/routes/event/FileEvent.java b/camel/src/main/java/com/github/theprez/manzan/routes/event/FileEvent.java index 4dca12a..e2a6021 100644 --- a/camel/src/main/java/com/github/theprez/manzan/routes/event/FileEvent.java +++ b/camel/src/main/java/com/github/theprez/manzan/routes/event/FileEvent.java @@ -43,13 +43,13 @@ public void configure() { .setHeader(EVENT_TYPE, constant(ManzanEventType.FILE)) .setBody(constant(m_file.getAbsolutePath())) .process((exchange) -> { - final File f = new File(exchange.getIn().getBody().toString()); + final File f = new File(getBody(exchange, String.class)); exchange.getIn().setBody(new FileNewContentsReader(f, "*TAG")); }) .split(body().tokenize("\n")).streaming().parallelProcessing(false).stopOnException() .convertBodyTo(String.class) .process(exchange -> { - String bodyStr = exchange.getIn().getBody(String.class); + String bodyStr = getBody(exchange, String.class); exchange.getIn().setHeader("abort", m_filter.matches(bodyStr) && StringUtils.isNonEmpty(bodyStr)?"continue":"abort"); }) .choice().when(simple("${header.abort} != 'abort'")) @@ -57,7 +57,7 @@ public void configure() { final Map data_map = new LinkedHashMap(); data_map.put(FILE_NAME, m_file.getName()); data_map.put(FILE_PATH, m_file.getAbsolutePath()); - data_map.put(FILE_DATA, exchange.getIn().getBody(String.class).replace("\r","")); + data_map.put(FILE_DATA, getBody(exchange, String.class).replace("\r","")); exchange.getIn().setHeader("data_map", data_map); exchange.getIn().setBody(data_map); }) diff --git a/docs/README.md b/docs/README.md index 35cb34c..a1d74ec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,7 +40,7 @@ Many other destinations will be available. Examples include: - [Google Drive](http://drive.google.com) ⏳ - [Google Mail (gmail)](http://gmail.com) ⏳ - [Google Pub/Sub](http://cloud.google.com/pubsub) ⏳ -- [Grafana Loki](https://grafana.com/oss/loki/) ⏳ +- [Grafana Loki](https://grafana.com/oss/loki/) ✅ - HTTP endpoints (REST, etc) ✅ - HTTPS endpoints (REST, etc) ✅ - [Internet of Things (mqtt)](https://www.eclipse.org/paho/) ⏳ diff --git a/docs/_sidebar.md b/docs/_sidebar.md index d009f93..4e36f22 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -9,5 +9,6 @@ * Examples * [File](config/examples/file.md) * [Twilio](config/examples/twilio.md) - * [Sentry watch](config/examples/sentry.md) + * [Sentry](config/examples/sentry.md) + * [Grafana Loki](config/examples/grafanaLoki.md) * [Contributing](contributing.md) \ No newline at end of file diff --git a/docs/config/dests.md b/docs/config/dests.md index 0fddb1c..fc46c56 100644 --- a/docs/config/dests.md +++ b/docs/config/dests.md @@ -20,15 +20,16 @@ As well, each section can provide `format` as an optional type. Some types have additional properties that they require. -| id | Description | Required properties | Optional properties | -|------------------|---------------------------------|------------------------------------------------------------| | -| `stdout` | Write all data to standard out. | None. | | -| `slack` | Send data to a Slack channel | * `webhook`
* `channel`
| | -| `fluentd` | Sent data to FluentD | * `tag`
* `host`
* `port`
| | -| `smtp`/`smtps` | Sent data via email | * `server`
* `subject`
* `to`
* `from`
| * `port` | -| `sentry` | Send data into Sentry | * `dsn` | | -| `twilio` | Send via SMS | * `sid`
* `token`
* `to`
* `from` | | -| `http`/`https` | Send data via http/https | * `url` | * `httpMethod`
* `x` where x is any query parameter | +| id | Description | Required properties | Optional properties | +|------------------|---------------------------------|------------------------------------------------------------| -------------------------------------------------------- | +| `stdout` | Write all data to standard out. | None. | | +| `slack` | Send data to a Slack channel | * `webhook`
* `channel` | | +| `fluentd` | Sent data to FluentD | * `tag`
* `host`
* `port` | | +| `smtp`/`smtps` | Sent data via email | * `server`
* `subject`
* `to`
* `from` | * `port` | +| `sentry` | Send data into Sentry | * `dsn` | | +| `twilio` | Send via SMS | * `sid`
* `token`
* `to`
* `from` | | +| `loki` | Send data into Grafana Loki | * `url`
* `username`
* `password`
| | +| `http`/`https` | Send data via http/https | * `url` | * `httpMethod`
* `x` where x is any query parameter | ### Example @@ -46,7 +47,7 @@ type=stdout [sentry_out] type=sentry -dsn= +dsn= [twilio_sms] type=twilio @@ -55,6 +56,12 @@ token=x to=+x from=+x +[loki_out] +type=loki +url= +username= +password= + [slackme] type=slack channel=open-source-system-status diff --git a/docs/config/examples/file.md b/docs/config/examples/file.md index d995871..5127747 100644 --- a/docs/config/examples/file.md +++ b/docs/config/examples/file.md @@ -1,8 +1,10 @@ +# File + This example shows how to use `file` as a data source. ## Configuration -Be sure to have read up on [Manzan configuration](/config/index.md) to understand where this files exist on your system. +Be sure to have read up on [Manzan configuration](/config/index.md) to understand where these files exist on your system. ### `app.ini` diff --git a/docs/config/examples/grafanaLoki.md b/docs/config/examples/grafanaLoki.md new file mode 100644 index 0000000..3b14aa5 --- /dev/null +++ b/docs/config/examples/grafanaLoki.md @@ -0,0 +1,34 @@ +# Grafana Loki + +This example shows how to use `loki` as a destination for a `watch` data source. + +## Configuration + +Be sure to have read up on [Manzan configuration](/config/index.md) to understand where these files exist on your system. + +### `data.ini` + +```ini +type=watch +id=sanjula +destinations=loki_out +format=$MESSAGE_ID$ (severity $SEVERITY$): $MESSAGE$ +strwch=WCHMSG((*ALL)) WCHMSGQ((*HSTLOG)) +``` + +### `dests.ini` + +```ini +[loki_out] +type=loki +url= +username= +password= +``` + +## Result + +
+ Grafana Loki 1 + Grafana Loki 2 +
\ No newline at end of file diff --git a/docs/config/examples/sentry.md b/docs/config/examples/sentry.md index 54e178a..58db9fa 100644 --- a/docs/config/examples/sentry.md +++ b/docs/config/examples/sentry.md @@ -1,8 +1,10 @@ -This example shows how to use `sentry` as a destination for a `watch` data source +# Sentry + +This example shows how to use `sentry` as a destination for a `watch` data source. ## Configuration -Be sure to have read up on [Manzan configuration](/config/index.md) to understand where this files exist on your system. +Be sure to have read up on [Manzan configuration](/config/index.md) to understand where these files exist on your system. ### `data.ini` @@ -20,11 +22,11 @@ strwch=WCHMSG((*ALL)) WCHMSGQ((*HSTLOG)) ```ini [sentry_out] type=sentry -dsn= +dsn= ``` ## Result -![](./images/sentry1.png) +![](../../images/sentry1.png) -![](./images/sentry2.png) \ No newline at end of file +![](../../images/sentry2.png) \ No newline at end of file diff --git a/docs/config/examples/twilio.md b/docs/config/examples/twilio.md index a917adb..2ed2058 100644 --- a/docs/config/examples/twilio.md +++ b/docs/config/examples/twilio.md @@ -1,8 +1,10 @@ +# Twilio + This example shows how to use `twilio` as a destination. ## Configuration -Be sure to have read up on [Manzan configuration](/config/index.md) to understand where this files exist on your system. +Be sure to have read up on [Manzan configuration](/config/index.md) to understand where these files exist on your system. ### `data.ini` @@ -40,4 +42,4 @@ from=+12058135364 ## Result -![](./images/twilio.png) \ No newline at end of file +![](../../images/twilio.png) \ No newline at end of file diff --git a/docs/images/grafanaLoki1.png b/docs/images/grafanaLoki1.png new file mode 100644 index 0000000..a4aaf45 Binary files /dev/null and b/docs/images/grafanaLoki1.png differ diff --git a/docs/images/grafanaLoki2.png b/docs/images/grafanaLoki2.png new file mode 100644 index 0000000..834cf07 Binary files /dev/null and b/docs/images/grafanaLoki2.png differ diff --git a/docs/config/examples/images/sentry1.png b/docs/images/sentry1.png similarity index 100% rename from docs/config/examples/images/sentry1.png rename to docs/images/sentry1.png diff --git a/docs/config/examples/images/sentry2.png b/docs/images/sentry2.png similarity index 100% rename from docs/config/examples/images/sentry2.png rename to docs/images/sentry2.png diff --git a/docs/config/examples/images/twilio.png b/docs/images/twilio.png similarity index 100% rename from docs/config/examples/images/twilio.png rename to docs/images/twilio.png