{"id":15351034,"url":"https://github.com/randgalt/maple","last_synced_at":"2025-04-15T12:33:41.036Z","repository":{"id":53604846,"uuid":"211975713","full_name":"Randgalt/maple","owner":"Randgalt","description":"Type-safe, consistently named and formatted, structured logging wrapper for SLF4J that's ideally suited for your logging aggregator.","archived":false,"fork":false,"pushed_at":"2023-12-05T22:22:43.000Z","size":132,"stargazers_count":56,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T20:12:18.665Z","etag":null,"topics":["elasticsearch","logging","logstash","slf4j","splunk"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Randgalt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-30T23:23:45.000Z","updated_at":"2024-09-14T17:56:03.000Z","dependencies_parsed_at":"2024-10-16T09:01:00.610Z","dependency_job_id":null,"html_url":"https://github.com/Randgalt/maple","commit_stats":{"total_commits":41,"total_committers":2,"mean_commits":20.5,"dds":"0.024390243902439046","last_synced_commit":"256c3690141be25c5284552125b5e1ad19db4faf"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Randgalt%2Fmaple","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Randgalt%2Fmaple/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Randgalt%2Fmaple/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Randgalt%2Fmaple/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Randgalt","download_url":"https://codeload.github.com/Randgalt/maple/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249072696,"owners_count":21208231,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elasticsearch","logging","logstash","slf4j","splunk"],"created_at":"2024-10-01T12:00:12.915Z","updated_at":"2025-04-15T12:33:41.016Z","avatar_url":"https://github.com/Randgalt.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/Randgalt/maple/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/Randgalt/maple/actions)\n[![Maven Central](https://img.shields.io/maven-central/v/io.soabase.maple/maple-parent.svg)](http://search.maven.org/#search%7Cga%7C1%7Cmaple-slf4j)\n\n# Maple\n\n***Type-safe, consistently named and formatted, structured logging wrapper for SLF4J that's ideally suited for your logging aggregator.***\n\n```java\nlog.info(schema -\u003e schema.id(userId).code(CODE_USER).qty(totalQty));\n```\n\n## Quickstart\n\n- Define a logging schema interface\n- Wrap an [SLF4J](https://www.slf4j.org) `Logger`\n- Begin structured logging\n\n*Define a logging schema interface*\n\n```java\npublic interface Logging {\n    Logging id(String id);\n    Logging fullName(String name);\n    Logging code(CodeType code);\n    Logging qty(int qty);\n    // etc\n}\n```\n\n*Wrap an SLF4J Logger*\n\n```java\nMapleLogger\u003cLogging\u003e logger = MapleFactory.getLogger(log, Logging.class);\n```\n\n*Begin structured logging*\n\n```java\nlogger.info(s -\u003e s.id(userId).fullName(\"Some Person\").code(CODE_USER).qty(totalQty));\n\n// translated into SLF4J call:\nslf4jLogger.info(\"id=XYZ123 full_name=\\\"Some Person\\\" code=user qty=1234\");\n```\n\n## Introduction\n\n[Per Thoughtworks](https://www.thoughtworks.com/radar/techniques/structured-logging) we should log in a structured manner...\n\n[Per Splunk](http://dev.splunk.com/view/logging/SP-CAAAFCK): \"Use clear key-value pairs. One of the most powerful features of Splunk software is its ability to extract fields from events when you search, creating structure out of unstructured data.\"\n\n[Per Elasticsearch](https://www.elastic.co/blog/structured-logging-filebeat): \"[Logging] works best when the logs are pre-parsed in a structured object, so you can search and aggregate on individual fields.\" It can already be done in Go or Python so why not Java?\n\nIf you export your logs to a centralized indexer, structuring your logging will make the indexer's job much easier and you will be able to get more and better information out of your logs. Manual structured logging is error prone and requires too much discipline. We can do better.\n\n## The Problem\n\nLog files are not individually read by humans. They are aggregated and indexed by systems such as Elasticsearch and Splunk. Free form text messages are not very useful for these systems. Instead, best practices dictate that logging be transformed into fields/values for better indexing, querying and alerting.\n\nLogging libraries have responded to this problem by providing APIs that make creating field/value logging easier. Much like Java's `String.format()` method you can put tokens in your log message to be replaced by runtime values. However, much like the difference between dynamically typed languages and strongly typed languages, token replacement is error prone, i.e.\n\n- It's easy to misspell field names\n- It's easy to transpose values in the replacement list\n- A field name in one part of the code might be spelled differently in another part of the code\n- It's hard to enforce required logging fields (e.g. \"event-type\")\n- No good way to prevent secure values such as passwords, keys, etc. from getting logged\n- Spaces, quotes, etc. need to be manually escaped\n\n## Structured Logging Library\n\n- ***Not a new logging library*** - merely a strongly typed wrapper for [SLF4J](https://www.slf4j.org)\n- Strongly typed logging model provides consistent naming and field/value mapping\n- Automatic escaping/quoting of values\n- Very low overhead\n- Optional support for:\n  - Object/model flattening\n  - Required fields\n  - \"Do Not Log\" fields\n  - Testing utilities\n  - Composed logging\n  - Consistent snake-case naming\n\n------\n\n# Documentation and Reference\n\n## Table of Contents\n\n- [Logging Schema](#logging-schema)\n- [MapleLogger](#maplelogger)\n- [Obtaining a MapleLogger Instance](#obtaining-a-maplelogger-instance)\n- [Logging Formatters](#logging-formatters)\n- [Additional Features](#additional-features)\n- [Examples](#examples)\n- [Unstructured Logging, Exceptions](#unstructured-logging-exceptions)\n- [Add To Your Project](#add-to-your-project)\n\n## Logging Schema\n\nA \"Logging Schema\" defines the field/values that you want to log. Depending on your needs, you can have one schema for your \nentire project, a few different schema for different parts of the code, etc. \n\nLogging Schema are Java interfaces. Schema should contain methods that each return the interface type and take exactly one \nargument. Thus each method describes a field (the method name) and a value (the method argument). Example:\n\n```java\npublic interface Logging {\n    Logging id(String id);\n    Logging fullName(String name);\n    Logging address(Address address);\n    Logging qty(int qty);\n}\n```\n\nFormatting/processing of schema arguments is controlled by a `MapleFormatter` (see the [Logging Formatters](#logging-formatters) section).\n\n## MapleLogger\n\nAt the heart of the library are instances of `MapleLogger`. These instances are parameterized with a [Logging Schema](#logging-schema), internally wrap SLF4J `Logger` instances and provide \nsimilar methods for logging at various levels. The methods allow for text messages and exceptions like SLF4J but, additionally, provide a [Logging Schema](#logging-schema) instance that can be filled for logging. \n\nHere's an example of using a MapleLogger instance versus an SLF4J logger instances:\n\n```java\nLogger slf4jLogger = LoggerFactory.getLogger(Foo.class);\nMapleLogger\u003cSchema\u003e mapleLogger = MapleFactory.getLogger(Foo.class, Schema.class);\n\n// logging only fields/values\nslf4jLogger.info(\"name={} age={}\", nameStr, theAge);\nmapleLogger.info(s -\u003e s.name(nameStr).age(theAge));\n\n// logging message, exception, fields/values\nslf4jLogger.info(\"Something Happened name={} age={}\", nameStr, theAge, exception);\nmapleLogger.info(\"Something Happened\", exception, s -\u003e s.name(nameStr).age(theAge));\n```\n\nNotes:\n\n- For each logging statement, a new logging schema is allocated \n- The logging schema allocation and execution only occurs if the logging level is enabled\n- The formatting of message, exception and logging schema into an actual log message is controlled by the currently configured [Logging Formatter](#logging-formatters)\n\n## Obtaining a MapleLogger Instance\n\nUse methods in `MapleFactory` to obtain instances of `MapleLogger` to use for logging.\n\n__MapleFactory__\n\n| Method | Description |\n| ------ | ----------- |\n| getLogger(Logger logger, Class\u0026lt;T\u003e schema) | Returns a structured logging instance that wraps the given SLF4J logger and provides an instance of the given schema class | \n| getLogger(Class\u0026lt;?\u003e clazz, Class\u0026lt;T\u003e schema) | Obtains an SLF4J logger via `LoggerFactory.getLogger(clazz)`, returns a structured logging instance that wraps it and provides an instance of the given schema class | \n| getLogger(String name, Class\u0026lt;T\u003e schema) | Obtains an SLF4J logger via `LoggerFactory.getLogger(name)`, returns a structured logging instance that wraps it and provides an instance of the given schema class | \n\n## Logging Formatters\n\nThe formatting of the log message is customizable. Two formatters are provided, `StandardFormatter` and `ModelFormatter`. You change the logging formatter used by calling \n`MapleFactory.setFormatter(...)`.\n\n_StandardFormatter_\n\nThe StandardFormatter formats the log in `field=value` pairs and has several options. Values can be quoted and/or escaped and the log main message can appear at the beginning or the end of the log string.\n\n_ModelFormatter_\n\nThe ModelFormatter extends _StandardFormatter_ to format all schema arguments as flattened model values. All arguments are passed to a provided Jackson ObjectMapper to serialize to a tree. The tree \ncomponents are flattened into schema values. With this formatter you can use an annotation to keep secret information from being logged.\nAnnotate any field (or corresponding getter) with `@DoNotLog`. See the [DoNotLog](#donotlog) section for details.\n\n## Additional Features\n\n### Required Values\n\nIf you would like to require certain schema values to not be omitted, annotate them with `@Required`. E.g.\n\n```java\npublic interface MySchema {\n    @Required\n    MySchema auth(String authValue);\n}\n```\n\nThe Structured Logger will throw `MissingSchemaValueException` if no value is set for required values. Note: if you want to only use this in development or pre-production, you can globally \ndisable required value checking by calling `MapleFactory.setProductionMode(true)`.\n\n### Ordering\n\nBy default, schema values are output in alphabetical order. Add `@SortOrder` annotations to change this. E.g.\n\n```java\npublic interface SchemaWithSort {\n    SchemaWithSort id(String id);\n\n    SchemaWithSort bool(boolean b);\n\n    @SortOrder(1)\n    SchemaWithSort qty(int qty);\n\n    @SortOrder(0)\n    SchemaWithSort zero(int z);\n}\n```\nThis schema will be output ala: `zero=xxx qty=xxx bool=xxx id=xxx`\n\n### Capture a Partial Value\n\nYou can pre-fill some values in the schema if needed. For example, you may want to use a request\nID in all logging in a method. This is done with the `concat()` method. E.g.\n\n```java\nMapleLogger\u003cSchema\u003e log = ...\n\n// in some method\n\nStatement\u003cSchema\u003e partial = s -\u003e s.requestId(id);\n\n// later\n\nlog.info(\"message\", partial.concat(s -\u003e s.code(c).name(n))); // request ID is also logged\n```\n\n### DoNotLog\n\nA Jackson annotation is provided to denote values that you do not want to be logged, `@DoNotLog`. If you use the \n`ModelFormatter` [Logging Formatters](#logging-formatters) (or your own Logging Formatter \nthat works with Jackson) use this annotation to mark fields that should not be logged.\n\nAnnotate your models\n\n```\npublic class Person {\n    private final String name;\n    \n    @DoNotLog\n    private final String password;\n    \n    // etc.\n}\n```\n\nCreate logging schema that use the model\n\n```\npublic interface Logging {\n    Logging person(Person p);\n    \n    Logging eventType(String type);\n    \n    ...\n}\n\n```\n\n### MDC\n\nYou can set [MDC](http://www.slf4j.org/api/org/slf4j/MDC.html) values using structured schema. E.g.\n\n```\n...\nMapleLogger\u003cSchema\u003e log = ...\n\n...\n\ntry (log.mdc(s -\u003e s.transactionId(id).code(c))) {\n    // do work - MDC values are removed afterwards\n}\n```\n\n_Using MDC as default values_\n\nYou can annotate schema methods with `@MdcDefaultValue`. For these methods if you do not specify\na value directly, Maple will look in the MDC for the value. E.g.\n\n```\npublic interface Schema {\n    ...\n    Schema name(String name);\n\n    @MdcDefaultValue\n    Schema transactionId(String id);\n    ...\n}\n\ntry (log.mdc(s -\u003e s.transactionId(id))) {\n    \n    log.info(s -\u003e s.name(n));   // transactionId is also logged here\n}\n\n``` \n\n### Unstructured Logging, Exceptions\n\nYou can include an unstructured message as well as any exceptions in your log statements. E.g.\n\n```java\nMapleLogger\u003cSchema\u003e log = ...\n\nlog.info(\"Any message you need\", s -\u003e s.event(e).qty(123));\n\n...\n\nlog.info(exception, s -\u003e s.event(e).qty(123));\n\n...\n\nlog.info(\"Any message you need\", exception, s -\u003e s.event(e).qty(123));\n```\n\nIf needed, you can also directly access the SLF4J logger. E.g.\n\n```java\nMapleLogger\u003cSchema\u003e log = ...\n\nlog.logger().info(\"Message: {}\", message, exception);\n```\n\n## Examples\n\nSeveral Examples are provided as a submodule to the project. See the [Examples Module](maple-examples) for details. \n\n## Add To Your Project\n\n| GroupID | ArtifactId |\n| ------- | ---------- |\n| `io.soabase.maple` | `maple-slf4j` |\n\nYou must also declare a dependency on SLF4J and an SLF4J compatible logging library. Additionally, if you will be using the \n`ModelFormatter` you must declare a dependency on Jackson.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandgalt%2Fmaple","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frandgalt%2Fmaple","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frandgalt%2Fmaple/lists"}