{"id":17491780,"url":"https://github.com/onjara/optic","last_synced_at":"2025-10-30T22:34:14.335Z","repository":{"id":42621000,"uuid":"269200369","full_name":"onjara/optic","owner":"onjara","description":"A powerful logging framework for Deno","archived":false,"fork":false,"pushed_at":"2024-11-04T22:17:09.000Z","size":388,"stargazers_count":50,"open_issues_count":4,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-09T11:32:24.636Z","etag":null,"topics":["deno","logging"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/onjara.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":"2020-06-03T21:40:39.000Z","updated_at":"2025-08-04T11:59:28.000Z","dependencies_parsed_at":"2024-11-02T00:19:41.131Z","dependency_job_id":"25219c24-947f-4aaa-a07e-838e6a2cd8d2","html_url":"https://github.com/onjara/optic","commit_stats":{"total_commits":186,"total_committers":1,"mean_commits":186.0,"dds":0.0,"last_synced_commit":"91a4ee04c9614a7da9b1fadd1d4daf2d0c077c30"},"previous_names":[],"tags_count":59,"template":false,"template_full_name":null,"purl":"pkg:github/onjara/optic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onjara%2Foptic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onjara%2Foptic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onjara%2Foptic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onjara%2Foptic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/onjara","download_url":"https://codeload.github.com/onjara/optic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/onjara%2Foptic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270130231,"owners_count":24532439,"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","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["deno","logging"],"created_at":"2024-10-19T08:04:57.502Z","updated_at":"2025-10-30T22:34:09.315Z","avatar_url":"https://github.com/onjara.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Optic\n\n[![ci](https://github.com/onjara/optic/workflows/ci/badge.svg)](https://github.com/onjara/optic)\n[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/optic/mod.ts)\n\nA powerful, highly extensible and easy to use logging framework for Deno.\n\n## At a glance\n\n- Easy to use fluid interface\n- Log anything\n- Modular and highly extensible - build your own streams, filters, transformers,\n  monitors, formatters and more\n- Deferred log message resolution for greater performance\n- Filters - keep your logs clean\n- Transformers - hide sensitive data, strip new lines, encode data, etc.\n- Monitors - watch your logs and take action\n- Profiling - measure the performance of your code\n\n## Table of contents\n\n1. [Quick start](#quick-start)\n2. [Logging](#logging)\n3. [Streams](#streams)\n4. [Log formatting](#log-formatting)\n5. [Monitors](#monitors)\n6. [Transformers](#transformers)\n7. [Filters](#filters)\n8. [Profiling](#profiling)\n\n## Quick start\n\n### Simple example\n\n```ts\nimport { Logger } from \"jsr:@onjara/optic/logger\";\n\nconst logger = new Logger();\nlogger.info(\"Hello world!\"); // outputs log record to the console\n```\n\n### Complete example\n\n```ts\nimport { every, FileStream, of } from \"jsr:@onjara/optic/streams\";\nimport {\n  between,\n  Level,\n  Logger,\n  LogRecord,\n  Stream,\n  TimeUnit,\n} from \"jsr:@onjara/optic/logger\";\nimport { JsonFormatter } from \"jsr:@onjara/optic/formatters\";\nimport { PropertyRedaction } from \"jsr:@onjara/optic/transformers\";\n\n// Configure the output file stream\nconst fileStream = new FileStream(\"logFile.txt\")\n  .withMinLogLevel(Level.Warn)\n  .withFormat(\n    new JsonFormatter()\n      .withPrettyPrintIndentation(2)\n      .withDateTimeFormat(\"YYYY.MM.DD hh:mm:ss:SSS\"),\n  )\n  .withBufferSize(10000)\n  .withLogFileInitMode(\"append\")\n  .withLogFileRotation(\n    every(200000).bytes().withLogFileRetentionPolicy(of(7).days()),\n  )\n  .withLogHeader(true)\n  .withLogFooter(true);\n\n// Configure the logger\nconst log = new Logger()\n  .withMinLogLevel(Level.Warn)\n  .addFilter((stream: Stream, logRecord: LogRecord) =\u003e logRecord.msg === \"spam\")\n  .addTransformer(new PropertyRedaction(\"password\"))\n  .addStream(fileStream);\n\n// \"info\" is lower than configured min log level of \"warn\"\nlog.info(\"Level too low. This won't be logged\");\n\n// logs \"Hello World\" and supporting metadata, returns \"Hello world\"\nconst logVal: string = log.critical(\"Hello world\", 12, true, { name: \"Poe\" });\n\n// Log records with `msg` of \"spam\" are filtered out\nlog.warn(\"spam\");\n\n// logs `msg` as { \"user\": \"jsmith\", \"password\": \"[Redacted]\" }\nlog.warn({ user: \"jsmith\", password: \"secret_password\" });\n\n// debug \u003c min log level, so function isn't evaluated and error not thrown\nlog.debug(() =\u003e {\n  throw new Error(\"I'm not thrown\");\n});\n\n// error \u003e min log level, so function is evaluated and `msg` is set to \"1234\"\nlog.error(() =\u003e {\n  return \"1234\";\n}); // logs \"1234\"\n\nconst x = 5;\nlog.if(x \u003e 10).error(\"Since x \u003c 10 this doesn't get logged\");\n\nlog.mark(\"before loop\");\nfor (let i = 0; i \u003c 1000000; i++) {\n  log.every(100).warn(\"Logs every 100th iteration\");\n  log.atMostEvery(10, TimeUnit.SECONDS).warn(\n    \"Logs at most once every 10 seconds\",\n  );\n}\nlog.mark(\"after loop\");\nlog.profilingConfig().withLogLevel(Level.Warn);\n// logs (at warn level) time, memory usage and ops called for loop duration\nlog.measure(between(\"before loop\", \"after loop\"));\n\nlog.enabled(false);\nlog.error(\"Logger is disabled, so this does nothing\");\n```\n\n## Logging\n\nAll logging in Optic is done through a logger instance, which provides the\ninterface and framework for all logging activity.\n\n**In this section:**\n\n1. [Creating a logger](#creating-a-logger)\n2. [Sharing loggers across modules](#sharing-loggers-across-modules)\n3. [Logging an event](#logging-an-event)\n4. [Log levels](#log-levels)\n5. [Log records](#log-records)\n6. [Minimum log level](#minimum-log-level)\n7. [Logging lifecycle](#logging-lifecycle)\n8. [In-line logging](#in-line-logging)\n9. [Deferred logging](#deferred-logging)\n10. [Conditional logging](#conditional-logging)\n11. [Deduplicating logs](#deduplicating-log-messages)\n12. [Rate limiting logs](#rate-limiting-the-logger)\n13. [Disabling the logger](#disabling-the-logger)\n14. [Shutting down the logger](#shutting-down-the-logger)\n\n### Creating a logger\n\nBefore you can log anything you must first get an instance of a logger.\n\n```ts\n// Using an unnamed logger\nconst defaultLogger = new Logger();\n\n// Using a named logger\nconst configLogger = new Logger(\"config\");\n```\n\n### Sharing loggers across modules\n\nTo reuse the same logger instance across multiple modules, declare and configure\nyour loggers in their own module which are then exported for use in other\nmodules. E.g.\n\n```ts\n/** logger.ts */\nimport { ConsoleStream, Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { JsonFormatter } from \"https://deno.land/x/optic/formatters/json.ts\";\n\nexport const logger = new Logger();\nlogger.addStream(new ConsoleStream().withFormat(new JsonFormatter()));\n```\n\n```ts\n/** module_a.ts */\nimport { logger } from \"./logger.ts\";\n\nlogger.info(\"hello world\");\n```\n\n### Logging an event\n\nYou can log an event through any of the level functions on the logger, supplying\na `msg` (of any type) and one or more optional `metadata` items. E.g.\n\n```ts\nlogger.info(\"File loaded\", \"exa_113.txt\", 1223, true);\n```\n\nIn this example, `\"File loaded\"` is the log record message (primary data), with\nsupporting metadata of the file name (`\"exa_113.txt\"`), size (`1223`) and\nreadonly attribute (`true`) supplied.\n\n### Log levels\n\nOptic supports the following logging levels out of the box:\n\n- Trace\n- Debug\n- Info\n- Warn\n- Error\n- Critical\n\nThese may be used directly via the logger, e.g.\n\n```ts\nlogger.trace(\"Some trace info\");\nlogger.error(\"Oops, something went wrong\");\n```\n\nOr through the use of the Level enum, e.g.\n\n```ts\nlogger.log(Level.Info, \"Here some info\");\n```\n\n### Log Records\n\nEach log event (e.g. `logger.info(\"hello world\")`) generates a `LogRecord` with\nall the relevant info on the event. Fields captured in the `LogRecord` are:\n\n| Field    | Description                                                       |\n| -------- | ----------------------------------------------------------------- |\n| msg      | The primary content of the log event of any type                  |\n| metadata | Supporting data, in one or more additional data items of any type |\n| dateTime | When the log record was created                                   |\n| level    | The log level of the event                                        |\n| logger   | The name of the logger which generated the event                  |\n\n### Minimum log level\n\nEach logger can be configured to log at a minimum level (the default level is\n`Debug`). Log events with a level lower than the minimum level are ignored with\nno action taken. There are 3 ways in which you can configure a logger to log at\na minimum level:\n\n#### Programmatically\n\nWithin the code, this can be set at any time and takes highest precedence of any\nmethod:\n\n```ts\nlogger.withLevel(Level.Warn);\n```\n\n#### Environment variable\n\nThrough the use of an environment variable `OPTIC_MIN_LEVEL` you can set the\nminimum log level of any logger. This method takes lowest priority and will be\noverridden if set programmatically or supplied via a command line argument. The\nvalues for this variable are any of the logging levels in uppercase, e.g.\n`Info`.\n\n**NOTE** for this method to work you MUST supply `--allow-env` to the Deno\ncommand line process. E.g.:\n\n```shell\nOPTIC_MIN_LEVEL=Error deno run --allow-env my-module.ts\n```\n\n#### Command line argument\n\nYou may also set the value of the minimum log level via a command line argument,\n`minLogLevel`. Minimum log levels set this way apply to all loggers unless\noverridden by programmatically setting a new level. Example:\n\n```shell\ndeno run my-module.ts minLogLevel=Error\n```\n\nThe value of the argument is any valid log level in Pascal case.\n\n### Logging lifecycle\n\nLogging events will undergo the following lifecycle:\n\n- If the minimum log level requirement is not met, the msg is returned with no\n  actions undertaken\n- Resolve the msg function value if using deferred logging (see below)\n- Run each registered monitor against the log record\n- For each stream\n  - Run each registered filter\n  - Run each registered transformer against the log record (if not filtered)\n  - Pass log record to stream for handling (if not filtered)\n- Return msg value (or resolved msg value in deferred logging)\n\n### In-line logging\n\nAll log statements return the value of the `msg` field, allowing more concise\ncoding. E.g.\n\n```ts\nconst user: User = logger.info(getUser());\n\n// is equivalent to:\nconst user: User = getUser();\nlogger.info(user);\n```\n\n### Deferred logging\n\nDeferred logging is used when you have expensive objects to create or calculate\nfor logging purposes, but don't want to incur the cost if the log message won't\nbe handled anyway. By supplying a function argument to the log event `msg`\nfield, this will defer resolution of the value of this function until after\ndetermining if the log event should be recorded. The resolved value is then set\nas the `msg` field in the LogRecord.\n\nExample:\n\n```ts\nconst value = logger.info(() =\u003e {\n  return expensiveObjectCreation();\n});\n```\n\nHere, `expensiveObjectCreation()` won't be called unless the logger is allowed\nto log info messages. `value`, in this example, will be set to the return value\nof `expensiveObjectCreation()` if the logger logged the message or `undefined`\nif it did not log it.\n\n### Conditional logging\n\nYou can specify a condition which must be met to log the log message. To do\nthis, supply a boolean condition to the `if` function on the logger. E.g.\n\n```ts\nlogger.if(attempts \u003e 3).warn(\"Excessive attempts by user\");\n```\n\nNote that even if the condition is true, the log record may not be logged if the\nminimum log level for the logger (and/or stream) is higher than this record.\n\n### Deduplicating log messages\n\nYou can use the logger to deduplicate log messages. When enabled, if 3 or more\nlog messages in a row are the same (same msg and supporting meta data) then\noutput of the duplicates is suppressed. Once a new log message which is\ndifferent is logged, then a new log message is finally cut (ahead of the\nnew/different one) detailing the number of duplicated log messages which were\nsuppressed. This can clean up your logs in situations where significant\nduplicates are encountered. Additionally, significant performance gains can\npotentially be had by not recording duplicate log messages over and over. Be\naware, however, that comparing the current log message to the previous isn't\nfree and if your logs have no or limited duplicates the performance may be\nreduced. If performance is critical it is suggested that you test the\ndeduplication capabilities. This feature is disabled by default.\n\nTo turn on deduping of logs:\n\n```ts\nlogger.withDedupe();\n```\n\nTo turn off deduping of logs:\n\n```ts\nlogger.withDedupe(false);\n```\n\nExample:\n\n```ts\nconst logger = new Logger().withDedupe();\nfor (let i = 0; i \u003c 1000; i++) {\n  logger.info(\"hello world\");\n}\n```\n\nOutput:\n\n```\n2021-01-11T22:32:58.497Z Info  hello world \n2021-01-11T22:32:58.506Z Info    ^-- last log repeated 999 additional times\n```\n\n### Rate limiting the logger\n\nYou can limit how many times the logger logs a particular statement in one of\ntwo ways;\n\n```ts\nlogger.atMostEvery(5, TimeUnit.SECONDS).info(\n  \"I'm only logged at most every 5 seconds\",\n);\nlogger.every(100).info(\"I'm logged every 100 attempts\");\n```\n\n**An important note**: Rate limiters work in a context. The context for the rate\nlimiting is determined, by default, on the amount, unit (for `atMostEvery`) and\nlog level. Where two or more `every` or `atMostEvery` statements match the same\ncontext, the same rate limiter will be used, possibly causing unintended side\neffects through race conditions on which of the statements will be logged when\nmatching or exceeding the constraint. To avoid this, you can enforce unique\ncontexts by passing in an optional context string:\n\n```ts\nlogger.atMostEvery(5, TimeUnit.SECONDS, \"Context 1\").info(\n  \"Logged at most every 5 seconds\",\n);\nlogger.atMostEvery(5, TimeUnit.SECONDS, \"Context 2\").info(\n  \"Also logged at most every 5 seconds\",\n);\n```\n\n### Disabling the logger\n\nYou can programmatically disable the logger which transforms it into a no-op\nlogger. This is useful for loggers which should not run in a production\nenvironment for example and the value of `enabled()` can be supplied via an\nenvironment variable or argument. The logger can later be re-enabled. When\ndisabled:\n\n- Logs are not logged and deferred log messages are not resolved\n- Adding or removing streams, filters, monitors or transformers is silently\n  ignored and they are not added (or removed)\n- Changes to the minimum log level are silently ignored\n- No tear down of streams, filters, monitors or transformers is undertaken upon\n  module unload or manual `shutdown()`. NOTE: this is a breaking change from\n  v1.3.6 or earlier which did execute tear down on module unload. Should you\n  wish to perform tear down actions on a disabled logger, you can either renable\n  the logger and manually call `logger.shutdown()` or call `destroy()` on each\n  stream, filter, monitor or transformer associated with the logger\n\nTo disable the logger:\n\n```ts\nlogger.enabled(false);\n```\n\n### Shutting down the logger\n\nUnder normal circumstances, when a Deno process completes it will fire an unload\nevent. The logger is registered to perform a shutdown when this event is\nbroadcast, meaning no explicit action is required by the client. During\nshutdown, the logger will call `destroy()` on all streams, filters, monitors or\ntransformers registered to the logger.\n\nSometimes, a client may wish to trigger a shutdown manually. For example, a\nclient may listen for certain signal events and request a shutdown manually via\n`logger.shutdown()`. Shutting down the logger fully disables it.\n\n## Streams\n\nStreams in Optic control the flow of log records from a module logging statement\nto an endpoint defined by the stream (e.g. console, file system, etc.). A logger\ncan have one or more streams and the same log message will be handled by all\nregistered streams (unless filtered from that stream).\n\n`ConsoleStream` is the default stream in a logger. Once any stream is added to\nthe logger, this default stream is removed. If console logging is still desired\nas an additional stream, you should explicitly add the `ConsoleStream`.\n\n### Optics streams\n\nThere are two out of the box streams available.\n\n#### Console stream\n\nA basic stream which outputs log messages to the console.\n\n```ts\nimport { ConsoleStream, Level, Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { TokenReplacer } from \"https://deno.land/x/optic/formatters/mod.ts\";\n\nconst consoleStream = new ConsoleStream()\n  .withMinLogLevel(Level.Debug)\n  .withLogHeader(true)\n  .withLogFooter(true)\n  .withFormat(\n    new TokenReplacer()\n      .withColor()\n      .withDateTimeFormat(\"YYYY.MM.DD hh:mm:ss:SSS\"),\n  );\n\nconst logger = new Logger().addStream(consoleStream);\n```\n\nSee [Formatting](#log-formatting) for further detail on formatting your logs.\n\n#### File stream\n\nA stream which outputs log messages to the file system.\n\n```ts\nimport { Level, Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { JsonFormatter } from \"https://deno.land/x/optic/formatters/mod.ts\";\nimport {\n  every,\n  FileStream,\n  of,\n} from \"https://deno.land/x/optic/streams/fileStream/mod.ts\";\n\nconst fileStream = new FileStream(\"./logFile.txt\")\n  .withMinLogLevel(Level.Warn)\n  .withFormat(new JsonFormatter())\n  .withBufferSize(30000)\n  .withLogFileInitMode(\"append\")\n  .withLogFileRotation(\n    every(2000000).bytes().withLogFileRetentionPolicy(of(7).days()),\n  )\n  .withLogHeader(true)\n  .withLogFooter(true);\n\nconst logger = new Logger().addStream(fileStream);\n```\n\nSee [FileStream documentation](./streams/fileStream/README.md) for full details.\nSee also [Formatting](#log-formatting) for further detail on formatting your\nlogs.\n\n### Defining a custom stream\n\nYou can build your own stream by creating a class which implements the\n[`Stream`](./types.ts) interface. The `handle` function is the only requirement\nwhich defines what your stream should do with a log record (and return true if\nthe record was handled).\n\nBasic example:\n\n```ts\nimport { Logger, LogRecord, Stream } from \"https://deno.land/x/optic/mod.ts\";\n\nclass SimpleStream implements Stream {\n  handle(logRecord: LogRecord): boolean {\n    console.log(logRecord.msg);\n    return true;\n  }\n}\n\nconst logger = new Logger().addStream(new SimpleStream());\n```\n\nStreams can also take logging metadata in `logHeader()` and `logFooter()`\nfunctions, and also can expose `setup()` and `destroy()` functions.\n\n## Log formatting\n\nOptic's streams allow you to format your logs however you wish, either through\nyour own custom formatting or several out of the box formatters. Formatters are\nset directly on the stream via `withFormat()`.\n\n### Optic formatters overview\n\nThree out of the box formatters are available. See also the\n[complete documentation on formatters](./formatters/README.md).\n\n#### TokenReplacer formatter\n\nThis formatter allows you to construct a custom string using tokens as\nplaceholders for the various log record fields.\n\nExample:\n\n```ts\nimport { ConsoleStream, Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { TokenReplacer } from \"https://deno.land/x/optic/formatters/mod.ts\";\n\nconst logger = new Logger().addStream(\n  new ConsoleStream()\n    .withFormat(\n      new TokenReplacer()\n        .withFormat(\"{dateTime} {level} {msg} {metadata}\")\n        .withDateTimeFormat(\"hh:mm:ss YYYY-MM-DD\")\n        .withLevelPadding(10)\n        .withColor(),\n    ),\n);\n```\n\nSee\n[TokenReplacer documentation](./formatters/README.md#tokenreplacer-formatter)\nfor full details.\n\n#### JSON formatter\n\nThis formatter allows you to output your log record as a structured JSON\nformatted string.\n\nExample:\n\n```ts\nimport { ConsoleStream, Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { JsonFormatter } from \"https://deno.land/x/optic/formatters/mod.ts\";\n\nconst logger = new Logger().addStream(\n  new ConsoleStream()\n    .withFormat(\n      new JsonFormatter()\n        .withFields([\"dateTime\", \"level\", \"logger\", \"msg\"])\n        .withDateTimeFormat(\"hh:mm:ss YYYY-MM-DD\")\n        .withPrettyPrintIndentation(2),\n    ),\n);\n```\n\nSee [JSON formatter documentation](./formatters/README.md#json-formatter) for\nfull details.\n\n#### DateTimeFormatter\n\nA formatter to be used within other formatters, this allows you to provide a\ncustom format for your date/time fields. Example:\n\n```ts\nlogger.addStream(\n  new ConsoleStream()\n    .withFormat(\n      new JsonFormatter().withDateTimeFormat(\"hh:mm:ss YYYY-MM-DD\"),\n    ),\n);\n```\n\nSee [DateTimeFormatter](./formatters/README.md#datetimeformatter) for full\ndetails.\n\n#### Custom formatters\n\nYou can also easily supply your own custom formatter by implementing the\n`Formatter` interface. See\n[Using your own custom formatter](./formatters/README.md#using-your-own-custom-formatter)\nfor full details.\n\n## Monitors\n\nMonitors allow you to spy on log records that flow through your logger. Monitors\nare run first, before any filtering, transformation or stream handling.\n\nSome use cases for monitors include:\n\n- Collect stats of your log records\n- Send alert if too many error records detected\n- Take automated action on specific error scenario\n- Debugging aid - e.g. output certain records to the console\n\n### Constructing a monitor\n\nThere are two ways to construct a monitor:\n\n#### Monitor function\n\nThis is a good choice for short and simple monitors. Monitor functions must\nmatch the following type:\n\n```ts\nexport type MonitorFn = (logRecord: LogRecord) =\u003e void;\n```\n\nExample:\n\n```ts\nimport { LogRecord, MonitorFn } from \"https://deno.land/x/optic/mod.ts\";\n\nconst mon: MonitorFn = (logRecord: LogRecord): void =\u003e {\n  if ((logRecord.msg as User).username === \"jsmith\") {\n    console.log(\"User jsmith spotted again\");\n  }\n};\n```\n\n#### Implement the Monitor interface\n\nThe Monitor interface requires implementation of the `check` function which is\nof type `MonitorFn` as above. This gives you the power of a class for more\ncomplex monitors.\n\n```ts\nimport { LogRecord, Monitor } from \"https://deno.land/x/optic/mod.ts\";\n\nclass UserMonitor implements Monitor {\n  check(logRecord: LogRecord): void {\n    if ((logRecord.msg as User).username === \"jsmith\") {\n      console.log(\"User jsmith spotted again\");\n    }\n  }\n}\n```\n\n### Registering Monitors\n\nMonitors are registered directly with the logger as follows:\n\n```ts\nconst logger = new Logger().addMonitor(new UserMonitor());\n```\n\n## Transformers\n\nOptic allows you to transform log records sent to a stream, allowing a log\nrecord to be transformed in one stream but not another. Transformation can\nchange some, all or none of the original log record. Transformation takes place\nafter monitors and also after log filtering but before the log record is sent to\na stream.\n\nSome use cases for transformation include:\n\n- Hiding sensitive data in your logs such as passwords or credit card details\n- Obscuring personal information, complying with data protection laws\n- Strip new lines from log data (log forging protection)\n- Encoding data\n- Compressing data\n\n### Constructing a transformer\n\nThere are two ways to construct an transformer.\n\n#### Transformer function\n\nThis is a good choice for short and simple transformers. Transformer functions\nmust match the following type:\n\n```ts\nexport type TransformerFn = (stream: Stream, logRecord: LogRecord) =\u003e LogRecord;\n```\n\nThe function takes a stream and logRecord and returns either the original log\nrecord if nothing is transformed, or a copy of the original with the necessary\ntransformations applied. Example:\n\n```ts\nimport {\n  LogRecord,\n  Stream,\n  TransformerFn,\n} from \"https://deno.land/x/optic/mod.ts\";\n\nconst tr: TransformerFn = (\n  stream: Stream,\n  logRecord: LogRecord,\n): LogRecord =\u003e ({\n  msg: (logRecord.msg as string).startsWith(\"password:\")\n    ? \"password: [Redacted]\"\n    : logRecord.msg,\n  metadata: [...logRecord.metadata],\n  level: logRecord.level,\n  logger: logRecord.logger,\n  dateTime: new Date(logRecord.dateTime.getTime()),\n});\n```\n\n#### Implement the Transformer interface\n\nThe Transformer interface requires implementation of the `transform` function,\nwhich is of type `TransformerFn` as above. This gives you the power of a class\nfor more complex transformations.\n\n```ts\nimport {\n  LogRecord,\n  Stream,\n  Transformer,\n} from \"https://deno.land/x/optic/mod.ts\";\n\nclass PasswordObfuscator implements Transformer {\n  transform(stream: Stream, logRecord: LogRecord): LogRecord {\n    if ((logRecord.msg as string).startsWith(\"password:\")) {\n      return {\n        msg: \"password: [Redacted]\",\n        metadata: [...logRecord.metadata],\n        level: logRecord.level,\n        logger: logRecord.logger,\n        dateTime: new Date(logRecord.dateTime.getTime()),\n      };\n    } else {\n      return logRecord;\n    }\n  }\n}\n```\n\n### Registering transformers\n\nTransformers are registered directly with the logger as follows:\n\n```ts\nconst passwordObfuscator = new PasswordObfuscator();\nconst logger = new Logger().addTransformer(passwordObfuscator);\n```\n\n### Optic transformers\n\nTwo out of the box transformers are available in Optic.\n\n#### Property redaction obfuscator\n\nThis transformer allows you to specify a single object property name which if\nfound in the `msg` or `metadata` log record fields (using deep object\nsearching), will replace the value of that property with the string\n`[Redacted]`. The original object is untouched, as transformation clones the\nobject before obfuscation.\n\n```ts\nimport { PropertyRedaction } from \"https://deno.land/x/optic/mod.ts\";\n\nlogger.addTransformer(new PropertyRedaction(\"password\"));\n\n// This next record is untouched by the transformer (no `password` property)\nlogger.info({ user: \"abc29002\", dateOfBirth: \"1966/02/33\" });\n\n// This record gets transformed to: {user: \"abc29002\", password: \"[Redacted]\"}\nlogger.info({ user: \"abc29002\", password: \"s3cr3tpwd\" });\n```\n\n#### Regular expression redaction\n\nThis obfuscator allows you to specify a regular expression and an optional\nreplacer function. The RegExpReplacer will then go through the `msg` and\n`metadata` fields looking for string values. Anytime it finds one, it will run\nthe Javascript `string.replace(regExp, replacer)` against it. For more details\non this, see\n[String.replace()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace).\n\nThere are two included replacer functions. `alphaNumericReplacer` (the default)\nwill replace all letters and numbers with `*`'s. `nonWhitespaceReplacer` will\nreplace all non white space characters with `*`'s. For both replacers, if the\nregular expression does not use groups then then entire match is replaced,\nhowever if groups are used, only the groups are replaced.\n\n```ts\nimport {\n  nonWhitespaceReplacer,\n  RegExpReplacer,\n} from \"https://deno.land/x/optic/transformers/regExpReplacer.ts\";\nimport { Logger } from \"https://deno.land/x/optic/mod.ts\";\n\nconst logger = new Logger()\n  .addTransformer(new RegExpReplacer(/£([\\d]+\\.[\\d]{2})/))\n  .addTransformer(new RegExpReplacer(/password: (.*)/, nonWhitespaceReplacer));\n\nlogger.info(\"Amount: £122.51\"); // becomes \"Amount: £***.**\" ('£' is not in a group)\nlogger.info(\"password: MyS3cret! Pwd!\"); // becomes \"password: ********* ****\"\n```\n\nRegExp and Replacer examples:\n\n| RegExp              | Test string | alphaNumericReplacer | nonWhitespaceReplacer |\n| ------------------- | ----------- | -------------------- | --------------------- |\n| /£([\\d]+\\.[\\d]{2})/ | £52.22      | £**.**               | £*****                |\n| /\\d{2}-\\d{2}-\\d{4}/ | 30-04-1954  | *\\*-\\*\\*-\\*\\*\\*\\*    | **********            |\n\n## Filters\n\nFilters allows you to filter out log records from your streams. A log record can\nbe filtered out from one stream but not another. Filters are processed after\nmonitors, but before obfuscators or stream handling. Upon handling a log record,\nthe logger will run filters once for each registered stream.\n\nSome use cases for filters include:\n\n- Preventing spam from filling up your logs\n- Directing log messages to certain streams only, based on content\n- Blocking malicious log records\n- Redirecting certain log records to an entirely different logger\n\n### Constructing a filter\n\nThere are two ways to construct a filter.\n\n#### Filter function\n\nThis is a good choice for short and simple filters. Filter functions must match\nthe following type:\n\n```ts\nexport type FilterFn = (stream: Stream, logRecord: LogRecord) =\u003e boolean;\n```\n\nThe function takes in a stream and logRecord and returns true if the logRecord\nshould be filtered out. Example:\n\n```ts\nimport { FilterFn, LogRecord, Stream } from \"https://deno.land/x/optic/mod.ts\";\nconst filter: FilterFn = (stream: Stream, logRecord: LogRecord) =\u003e\n  (logRecord.msg as string).includes(\"bad stuff\");\n```\n\n#### Implement the Filter interface\n\nThe Filter interface requires you to implement the `shouldFilterOut` function,\nwhich is of type `FilterFn` as above. This gives you the power of a class for\nmore complex filtering, or perhaps you want to redirect filtered out logs to\nanother logger and stream.\n\n```ts\nimport { Filter, LogRecord, Stream } from \"https://deno.land/x/optic/mod.ts\";\n\nclass MyFilter implements Filter {\n  shouldFilterOut(stream: Stream, logRecord: LogRecord): boolean {\n    return (logRecord.msg as string).includes(\"bad stuff\");\n  }\n}\n```\n\n### Registering filters\n\nFilters are registered directly with the logger as follows:\n\n```ts\nconst myFilter = new MyFilter();\nconst logger = new Logger().addFilter(myFilter);\n```\n\n### Optic filters\n\nTwo out of the box filters are available in Optic.\n\n#### Regular Expression Filter\n\nThis filter takes in a regular expression. If it matches, then the log record is\nfiltered out. The log record `msg` and `metadata` fields are first converted to\na string if necessary before testing the regular expression.\n\n```ts\nimport { Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { RegExpFilter } from \"https://deno.land/x/optic/filters/regExpFilter.ts\";\n\n// Filters out log records containing `%` or `\u0026` in the message or metadata\nconst regExpFilter = new RegExpFilter(/[%\u0026]+/);\nconst logger = new Logger().addFilter(regExpFilter);\nlogger.error(\"Oh no!\"); // not filtered\nlogger.error(\"Oh no!\", \"\u0026 another thing\"); // filtered out\n```\n\n#### Substring filter\n\nThis filter takes in a string. If this string is found to be a substring of\neither the log record `msg` or `metadata` fields (converting them to string\nfirst if required), then this log record is filtered out. Example:\n\n```ts\nimport { Logger } from \"https://deno.land/x/optic/mod.ts\";\nimport { SubStringFilter } from \"https://deno.land/x/optic/filters/subStringFilter.ts\";\n\nconst subStringFilter = new SubStringFilter(\"user1234\");\nconst logger = new Logger().addFilter(subStringFilter);\nlogger.info({ user: \"joe1944\", action: \"login\" }); // not filtered\nlogger.info({ user: \"user1234\", action: \"login\" }); // filtered out\n```\n\n## Profiling\n\nOptic's profiling capabilities allow you to measure your code's performance.\nProfiling is based on marks which are recorded at various points in your code\nbase. A mark is a snapshot of your running application, recording the time,\nmemory consumption and ops calls (these are the calls between the V8 engine and\nDeno runtime). Measures are then used to output differences between two marks to\nyour logs.\n\n### Recording marks\n\nRecording marks in your code is very straightforward. The example below takes a\nsnapshot of the time, memory usage and ops calls and saves these against an\nidentifier (e.g. `below`).\n\n```ts\nlogger.mark(\"before\");\nsomeFunction();\nlogger.mark(\"after\");\n```\n\n### Measuring\n\nMeasuring outputs the difference between two marks to your logs. For example,\nthis will measure the performance of `someFunction()`:\n\n```ts\nimport { between } from \"https://deno.land/x/optic/mod.ts\";\n\nlogger.mark(\"before\");\nsomeFunction();\nlogger.mark(\"after\");\n\nlogger.measure(between(\"before\", \"after\"), \"with description\");\n```\n\n#### Sample output\n\nBy default, profiling uses the `SummaryMeasureFormatter` to format the measure\nlog output. Example output:\n\n```ts\n//Measuring 'before' -\u003e 'after' (with description), took 790ms; heap usage increased 9.2 MB to 11.7 MB; 18 ops dispatched, all completed\n```\n\n#### Special marks\n\nThere are two special marks which can be used, `NOW` and `PROCESS_START`. The\nspecial mark `NOW` will generate a mark at the time of measurement, while\n`PROCESS_START` represents the moment the process started (with 0ms time, 0\nmemory usage and 0 ops calls). These special marks do not require you to use the\n`mark` function in the logger first.\n\n#### Measuring examples\n\n```ts\nimport {\n  between,\n  from,\n  NOW,\n  PROCESS_START,\n  to,\n} from \"https://deno.land/x/optic/mod.ts\";\n\nlogger.mark(\"before\");\nsomeFunction();\nlogger.mark(\"after\");\n\n//Measure between two marks (with optional description)\nlogger.measure(between(\"before\", \"after\"));\nlogger.measure(between(PROCESS_START, \"after\"));\nlogger.measure(between(\"before\", NOW), \"with description\");\n\n//Measure from a mark to NOW (with optional description)\nlogger.measure(from(\"before\"));\nlogger.measure(from(\"before\"), \"with description\");\n\n//Measure from PROCESS_START to mark (with optional description)\nlogger.measure(to(\"after\"));\nlogger.measure(to(\"after\"), \"with description\");\n\n//Mesure from PROCESS_START to NOW (with optional description)\nlogger.measure();\nlogger.measure(\"with description\");\n```\n\n### Configuring the profiler\n\nThere are a number of options for you to configure the profiler with if you\nwish.\n\n```ts\n//Defaults shown below\nlogger.profilingConfig()\n  .enabled(true) //Enable or disable all recording of marks or measures\n  .captureMemory(true) //Enable or disable capturing of memory information\n  .captureOps(true) //Enable or disable capturing of ops calls\n  .withLogLevel(Level.Info) //Set the log level at which the profile measure is output\n  .withFormatter(new SummaryMeasureFormatter()); //Formats the profiling log message\n```\n\n### Custom measure output\n\nAs described above, by default the `SummaryMeasureFormatter` is used to format\nthe output. You can specify your own measure formatter by implementing the\ninterface `MeasureFormatter`:\n\n```ts\ninterface MeasureFormatter\u003cT\u003e {\n  format(startMark: ProfileMark, endMark: ProfileMark, label?: string): T;\n}\n```\n\nExample:\n\n```ts\nimport { MeasureFormatter } from \"https://deno.land/x/optic/mod.ts\";\n\nconst myFormatter: MeasureFormatter\u003cstring\u003e = {\n  format(startMark: ProfileMark, endMark: ProfileMark, label?: string): string {\n    return (endMark.timestamp - startMark.timestamp) + \"ms\" +\n      (label ? (\" \" + label) : \"\");\n  },\n};\nlog.profilingConfig().withFormatter(myFormatter);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonjara%2Foptic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fonjara%2Foptic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fonjara%2Foptic/lists"}