{"id":16754277,"url":"https://github.com/purplenoodlesoop/mark","last_synced_at":"2025-04-10T16:13:22.300Z","repository":{"id":65189397,"uuid":"586610468","full_name":"purplenoodlesoop/mark","owner":"purplenoodlesoop","description":"📜 Extensible and customizable logging framework for Dart and Flutter.","archived":false,"fork":false,"pushed_at":"2023-04-20T21:38:07.000Z","size":20,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T14:04:44.562Z","etag":null,"topics":["dart","flutter","framework","logger","logging"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/purplenoodlesoop.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2023-01-08T18:28:47.000Z","updated_at":"2024-09-14T09:11:29.000Z","dependencies_parsed_at":"2025-02-17T13:37:17.170Z","dependency_job_id":null,"html_url":"https://github.com/purplenoodlesoop/mark","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purplenoodlesoop%2Fmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purplenoodlesoop%2Fmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purplenoodlesoop%2Fmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/purplenoodlesoop%2Fmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/purplenoodlesoop","download_url":"https://codeload.github.com/purplenoodlesoop/mark/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248251455,"owners_count":21072689,"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":["dart","flutter","framework","logger","logging"],"created_at":"2024-10-13T03:04:21.874Z","updated_at":"2025-04-10T16:13:22.284Z","avatar_url":"https://github.com/purplenoodlesoop.png","language":"Dart","readme":"# mark\n\n[![Pub](https://img.shields.io/pub/v/mark.svg)](https://pub.dev/packages/mark)\n[![GitHub Stars](https://img.shields.io/github/stars/purplenoodlesoop/mark.svg)](https://github.com/purplenoodlesoop/mark)\n[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://en.wikipedia.org/wiki/MIT_License)\n[![Linter](https://img.shields.io/badge/style-custom-brightgreen)](https://github.com/purplenoodlesoop/stream-bloc/blob/master/analysis_options.yaml)\n[![Code size](https://img.shields.io/github/languages/code-size/purplenoodlesoop/mark)](https://github.com/purplenoodlesoop/mark)\n\n---\n\nExtensible and customizable logging framework for Dart and Flutter.\n\n## Index\n\n- [Index](#index)\n- [About](#about)\n- [Motivation](#motivation)\n- [Install](#install)\n- [Usage](#usage)\n  - [Basic](#basic)\n  - [Advanced](#advanced)\n    - [Processors](#processors)\n      - [Custom processor](#custom-processor)\n      - [Dynamically stating processors](#dynamically-stating-processors)\n      - [Forking the logger](#forking-the-logger)\n    - [Messages](#messages)\n    - [Meta](#meta)\n  - [Extras](#extras)\n    - [Pattern matching](#pattern-matching)\n    - [Formatter mixins](#formatter-mixins)\n\n## About\n\n`mark` is a logging framework for Dart. It is designed to be extensible and customizable, allowing the creation of custom logging messages and processors. It is also designed to be easy to use, with a simple API and a default configuration that is ready to use. \n\n## Motivation\n\nCurrent logging solutions either do not provide enough customization, forcing you to use ad-hoc solutions, lack specific features, such as scoping, or lack the desired Developer Experience (DX). `mark` aims to solve these problems by providing a simple, yet powerful, logging framework that is easy to use, customize, extend and integrate.\n\n`mark` tries to be as unopinionated as possible, allowing you to use it in any way you want. It is designed to be used in any Dart project, from small CLI tools to large Flutter applications, whilst trying to take care of the most common use cases and striving to provide a \"framework\" solution.\n\n## Install\n\nAdd `mark` to your `pubspec.yaml` file:\n\n```yaml\ndependencies:\n  mark: \"current version\"\n```\n\nOr do it via CLI.\n\nFor Flutter projects:\n\n```bash\n$ flutter pub add mark\n```\n\nFor Dart projects:\n\n```bash\n$ dart pub add mark\n```\n## Usage\n\n`mark` can be fitted to the needs of the project by utilizing only the needed features. \n\nThe main actors of the framework are:\n  - `Logger` - a class that is used to log messages\n  - `LogMessage` - a class that represents a log message and is passed to the `Logger`\n  - `MessageProcessor` - a class that processes a `LogMessage`\n\nThe `Logger` is the main actor of the framework, which is used to log messages. The `Logger` is configured with a list of `MessageProcessor`s, which are used to process the `LogMessage`s. This approach allows to create of custom `MessageProcessor`s, which can be used to customize the logging process, filter messages (as the list of `MessageProcessor`s can be configured dynamically), and process the messages in any way – from printing them to the console to sending them to a remote server.\n\n### Basic\n\nThe most basic usage of `mark` is to use the default configuration. This can be done by importing the `mark` package, creating a global logger constant with a default message processor, and using the default logging methods:\n\n```dart\nfinal logger = Logger(processors: const [EphemeralMessageProcessor()]);\n\nvoid main() {\n  logger.info('Hello, World!');\n}\n```\n\nThe `EphemeralMessageProcessor` is a default message processor, which prints the message to the console with a platform-specific implementation of the source, and defining the logger as a global constant allows to use it in any part of the code, without the need to pass it as a parameter.\n\nSince this object is a singleton, the disposing can be ignored. For loggers that are not singletons, the `dispose` method should be called to dispose of the resources used by the logger.\n\nThe `Logger` class provides a list of default logging methods, which really just call the `mark` method with an appropriate `LogMessage` type: `InfoMessage` for `info`, `WarningMessage` for `warning`, `ErrorMessage` for `error`, and `DebugMessage` for `debug`.\n\n\n### Advanced\n\nThe default configuration is not always enough, and `mark` allows to create custom messages and processors, which can be used to customize the logging process. Most commonly, processors are customized in the first place, as they are the main actors of the logging process. \n\nCustomization of the logging process can be done by creating a custom `MessageProcessor`, which can be used to filter messages, process them in any way, such as sending them to a remote server, and change the set of processors dynamically.\n\n#### Processors\n\nThe `MessageProcessor` is a class that processes a `LogMessage`. It is a simple class, which has a single method, `processMessages`, which takes a `Stream\u003cLogMessage\u003e` and returns a `Stream\u003cvoid\u003e`. The `processMessages` method is called by the `Logger` when a message is logged.\n\n##### Custom processor\n\nTo create a custom message processor, a `BaseMessageProcessor` class can be extended, which allows to select a subset of messages to process and to implement the way how messages are formatted, processed and the order of the processing by overriding the appropriate methods.\n\nA custom message processor that sends messages to a remote server can be created as follows:\n\n```dart\nclass RemoteMessageProcessor\n    extends BaseMessageProcessor\u003cLogMessage, Map\u003cString, dynamic\u003e\u003e { // 1\n  final RemoteLogService service;\n\n  const RemoteMessageProcessor(this.service);\n\n  @override\n  Map\u003cString, dynamic\u003e format(LogMessage message) =\u003e message.toJson(); // 2\n\n  @override\n  Future\u003cvoid\u003e process(\n    LogMessage message,\n    Map\u003cString, dynamic\u003e formattedMessage,\n  ) =\u003e\n      service.send(formattedMessage); // 3\n\n  @override\n  bool allow(LogMessage message) =\u003e\n      message.severityValue \u003e= ErrorMessage.severity; // 4\n\n  @override\n  Stream\u003cvoid\u003e transform(\n    EntryProcessorF\u003cLogMessage, Map\u003cString, dynamic\u003e\u003e processorF,\n    Stream\u003cLogEntry\u003cLogMessage, Map\u003cString, dynamic\u003e\u003e\u003e entries,\n  ) =\u003e\n      entries.asyncMap(processorF); // 5\n}\n\n```\n\n1. The `BaseMessageProcessor` class takes two type parameters, the first one is the type of the message, and the second one is the type of the formatted message. The `RemoteMessageProcessor` is a generic class, which takes a `LogMessage` as a message type and a `Map\u003cString, dynamic\u003e` as a formatted message type. The `LogMessage` is the default message type, which is used by the `Logger` and allows for all messages, and the `Map\u003cString, dynamic\u003e` is a type that is used to send messages to a remote server.\n\n2. The `format` method is used to format the message. It takes a `LogMessage` and returns a `Map\u003cString, dynamic\u003e`, which is used to send messages to a remote server.\n\n3. The `process` method is used to process the formatted message. It takes a `LogMessage` and a formatted message, which is a `Map\u003cString, dynamic\u003e`, and returns a `FutureOr\u003cvoid\u003e`, (a `Future\u003cvoid\u003e` in this case) which allows processing the message in any way.\n\n4. The `allow` method is used to filter messages. It takes a `LogMessage` and returns a `bool`, which allows filtering messages. In this case, only messages with a severity of `ErrorMessage` or higher are allowed.\n\n5. The `transform` method is used to specify the order in which messages are processed. It takes an `EntryProcessorF` and a `Stream\u003cLogEntry\u003e`, and returns a `Stream\u003cvoid\u003e`. The `EntryProcessorF` is a function that takes a `LogEntry` and returns a `FutureOr\u003cvoid\u003e`. In this case, the messages are processed in the order in which they are received.\n\n##### Dynamically stating processors\n\nProcessors are specified at the creation of `Logger`, and the list of processors can be assembled dynamically, for example by utilizing Dart's features in regard to conditional list entries. A Logger that prints messages to the console in debug and profile modes, and sends them to a remote server in release mode can be created as follows:\n\n```dart\n\nfinal logger = Logger(\n  processors: [\n    if (kReleaseMode)\n      const RemoteMessageProcessor()\n    else\n      const EphemeralMessageProcessor(),\n  ],\n);\n\n```\n\n##### Forking the logger\n\nThe `fork` method of the `Logger` class allows to create a new `Logger` with an additional set of processors. Since the `Logger` object is immutable, altering the list of processors is not possible, and the `fork` method allows one to add them by creating a new `Logger` object.\n\nIt is important to always dispose of the `Logger` object, which is done by calling the `dispose` method. The `fork` method returns a new `Logger` object, which should be disposed of separately.\n\nFor example, this feature can be used to granularly improve traceability in a function with known bugs.\n\n```dart\n\nFuture\u003cvoid\u003e main() async {\n  final logger = Logger( // 1\n    processors: const [\n      EphemeralMessageProcessor(),\n    ],\n  );\n  final remoteLogger = logger.fork( // 2\n    processors: const [\n      RemoteMessageProcessor(),\n    ],\n  );\n\n  await buggyFunction(remoteLogger); // 3\n\n  await remoteLogger.dispose(); // 4\n  await logger.dispose();\n}\n```\n\n1. The `Logger` object is created with a single processor, which prints messages to the console. This object can be viewed as a base, root logger.\n\n2. The `fork` method is used to create a new `Logger` object with an additional set of processors. In this case, the `RemoteMessageProcessor` is added to the list of processors.\n\n3. The `buggyFunction` is called with the `remoteLogger`, which allows to print messages to the console AND send them to a remote server.\n\n4. All logger objects are disposed of after they are no longer needed.\n\n#### Messages\n\nIn addition to custom processors, custom messages can be created. The `LogMessage` class is an interface that allows to create custom messages. Every log message has a severity, a stack trace, data, and an optional meta field. The severity is used to filter messages, the stack trace is used to provide traceability, the data is used to provide a message payload, and the meta field is used to provide additional information. The `LogMessage` is serializable via the `toJson()` method;\n\nTo create a custom message, a `BaseLogMessage` should be extended, which implements the `LogMessage` interface, and provides a default implementation of the fields, as well as a `toJson` method.\n\nAn example message of a login event can be described as follows:\n\n```dart\n\nclass LoginEvent extends BaseLogMessage {\n  static const int severity = 3;\n\n  @override\n  final String data;\n\n  LoginEvent(String email, {super.stackTrace, super.meta}) : data = email;\n\n  @override\n  @override\n  int get severityValue =\u003e severity;\n}\n\n```\n\nHowever, usually custom events are represented as a union type, which can be used in a custom message processor to process messages only of a selected type.\n\n#### Meta \n\nIn addition, the `meta` field can be used to provide additional information about the message. Usually, it can be passed directly to the constructor of a `LogMessage`, but a `Zone` injection is also an option. The `ZonedMeta` namespace can be used to create a new Zone with a passed meta. \n\n```dart\nZonedMeta.attach('I came from the Zone!', body);\n```\n\nIn this example, the `body` function will be executed in a new Zone, which will have the `I came from the Zone!` meta attached to it, in every message that leaves the constructor meta field empty.\n\n\n### Extras \n\nA few uncategorized extras are provided by the `mark` package.\n\n#### Pattern matching\n\nBoth `LogMessage` and the `PrimitiveLogMessage` can be pattern-matched to a more specific type. The `LogMessage` can be pattern-matched to a `PrimitiveLogMessage` or a `Log`, and the `PrimitiveLogMessage` can be pattern-matched to a concrete primitive message type, such as `InfoMessage` or `DebugMessage`.\n\nAn `EphemeralMessageProcessor`'s web implementation can be used as an example of pattern-matching.\n\n```dart\nvoid Function(Object? data) _matchPrinter(LogMessage message) {\n  final console = window.console;\n  final info = console.info;\n\n  return message.matchPrimitive(\n    primitive: (message) =\u003e message.match(\n      info: (_) =\u003e info,\n      debug: (_) =\u003e console.debug,\n      warning: (_) =\u003e console.warn,\n      error: (_) =\u003e console.error,\n    ),\n    orElse: (_) =\u003e info,\n  );\n}\n```\n\n#### Formatter mixins\n\nThe `mark` package provides a few formatter mixins, which can be used to format messages in a specific way based on a specified output type parameter:\n  - `JsonMessageFormatterMixin` - formats messages to `Map\u003cString, Object?\u003e` using the `toJson` method.\n  - `StringMessageFormatterMixin` – formats messages to `String` using the `toString` method.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurplenoodlesoop%2Fmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpurplenoodlesoop%2Fmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpurplenoodlesoop%2Fmark/lists"}