{"id":17664434,"url":"https://github.com/tilfin/ougai","last_synced_at":"2025-04-14T02:57:50.263Z","repository":{"id":12129463,"uuid":"71053566","full_name":"tilfin/ougai","owner":"tilfin","description":"A Ruby structured logging is capable of handling a message, custom data or an exception easily and generates JSON or human readable logs.","archived":false,"fork":false,"pushed_at":"2025-02-16T04:50:58.000Z","size":225,"stargazers_count":265,"open_issues_count":11,"forks_count":24,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-14T02:57:45.932Z","etag":null,"topics":["awesome-print","bunyan","fluentd-logger","gem","json-logging","logentries","logger","loggly","pino","rails-log","ruby","structured-logging"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/tilfin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2016-10-16T13:55:38.000Z","updated_at":"2025-04-08T16:10:18.000Z","dependencies_parsed_at":"2025-03-02T17:00:28.645Z","dependency_job_id":"07f4495f-bbf3-4910-a098-2b01b7e2efe6","html_url":"https://github.com/tilfin/ougai","commit_stats":null,"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilfin%2Fougai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilfin%2Fougai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilfin%2Fougai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tilfin%2Fougai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tilfin","download_url":"https://codeload.github.com/tilfin/ougai/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248813786,"owners_count":21165632,"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":["awesome-print","bunyan","fluentd-logger","gem","json-logging","logentries","logger","loggly","pino","rails-log","ruby","structured-logging"],"created_at":"2024-10-23T20:05:25.660Z","updated_at":"2025-04-14T02:57:50.241Z","avatar_url":"https://github.com/tilfin.png","language":"Ruby","readme":"Ougai\n=====\n\n[![Gem Version](https://badge.fury.io/rb/ougai.svg)](https://badge.fury.io/rb/ougai)\n[![document](https://img.shields.io/badge/document-2.0.0-green.svg)](http://www.rubydoc.info/gems/ougai/)\n[![CI](https://github.com/tilfin/ougai/actions/workflows/ci.yml/badge.svg)](https://github.com/tilfin/ougai/actions/workflows/ci.yml)\n[![Code Climate](https://codeclimate.com/github/tilfin/ougai/badges/gpa.svg)](https://codeclimate.com/github/tilfin/ougai)\n[![Test Coverage](https://codeclimate.com/github/tilfin/ougai/badges/coverage.svg)](https://codeclimate.com/github/tilfin/ougai/coverage)\n\nA structured logging system that is capable of handling a message, structured data, or an exception easily.\nIt has JSON formatters compatible with [Bunyan](https://github.com/trentm/node-bunyan) or [pino](https://github.com/pinojs/pino) for Node.js, and a\nhuman-readable formatter with [Amazing Print](https://github.com/amazing-print/amazing_print) for the console.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'ougai'\n```\n\nAnd then execute:\n\n```\n$ bundle\n```\n\nOr install it yourself as:\n\n```\n$ gem install ougai\n```\n\n## Usage\n\n**Ougai::Logger** is a sub-class of the standard [Logger](https://ruby-doc.org/stdlib-2.4.1/libdoc/logger/rdoc/Logger.html) in Ruby.\nAll arguments of the `initialize` pass through to **::Logger**.\n\n```ruby\nrequire 'ougai'\n\nlogger = Ougai::Logger.new($stdout)\n```\n\n### TRACE level\n\nThe `level` of logger supports **TRACE** level lower than **DEBUG**.\n\n```ruby\nlogger.level = Ougai::Logger::TRACE # , :trace or 'trace'\n```\n\n### log only a message\n\n```ruby\nlogger.info('Information!')\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":14607,\"level\":30,\"time\":\"2016-10-16T22:26:48.835+09:00\",\"v\":0,\"msg\":\"Information!\"}\n```\n\n### log only structured data\n\n```ruby\nlogger.info({\n  msg: 'Request', method: 'GET', path: '/login',\n  format: 'html', controller: 'LoginController',\n  action: 'new', status: 200\n})\nlogger.debug(user: { name: 'Taro', age: 19 })\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":9044,\"level\":30,\"time\":\"2016-10-28T17:58:53.668+09:00\",\"v\":0,\"msg\":\"Request\",\"method\":\"GET\",\"path\":\"/login\",\"format\":\"html\",\"controller\":\"LoginController\",\"action\":\"new\",\"status\":200}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":9044,\"level\":20,\"time\":\"2016-10-28T17:58:53.668+09:00\",\"v\":0,\"msg\":\"No message\",\"user\":{\"name\":\"Taro\",\"age\":19}}\n```\n\nIf a data does not contain `msg` field, msg is set `default_message` attribute value of a Logger. its default is 'No message'.\n\n```ruby\nlogger.default_message = 'User dump'\nlogger.debug(user: { name: 'Taro', age: 19 })\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":9303,\"level\":20,\"time\":\"2016-10-28T18:03:50.118+09:00\",\"v\":0,\"msg\":\"User dump\",\"user\":{\"name\":\"Taro\",\"age\":19}}\n```\n\n### log only an exception\n\n```ruby\nbegin\n  raise StandardError, 'some error'\nrescue =\u003e ex\n  logger.error(ex)\nend\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":4422,\"level\":50,\"time\":\"2016-10-22T13:05:02.989+09:00\",\"v\":0,\"msg\":\"some error\",\"err\":{\"name\":\"StandardError\",\"message\":\"some error\",\"stack\":\"main.rb:24:in `\u003cmain\u003e'\"}}\n```\n\n### log with a message and custom data\n\n```ruby\nlogger.debug('Debugging', data_id: 1, data_flag: true)\nlogger.debug('Debug!', custom_data: { id: 1, name: 'something' })\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":14607,\"level\":20,\"time\":\"2016-10-16T22:26:48.836+09:00\",\"v\":0,\"msg\":\"Debugging\",\"data_id\":1,\"data_flag\":true}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":14607,\"level\":20,\"time\":\"2016-10-16T22:26:48.836+09:00\",\"v\":0,\"msg\":\"Debug!\",\"custom_data\":{\"id\":1,\"name\":\"something\"}}\n```\n\n### log with a message and an exception\n\n```ruby\nbegin\n  raise StandardError, 'fatal error'\nrescue =\u003e ex\n  logger.fatal('Unexpected!', ex)\nend\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":14607,\"level\":60,\"time\":\"2016-10-16T22:26:48.836+09:00\",\"v\":0,\"msg\":\"Unexpected!\",\"err\":{\"name\":\"StandardError\",\"message\":\"fatal error\",\"stack\":\"main.rb:12:in `\u003cmain\u003e'\"}}\n```\n\n### log with an exception and custom data\n\n```ruby\nbegin\n  raise StandardError, 'some error'\nrescue =\u003e ex\n  logger.error(ex, error_id: 999)\nend\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":13962,\"level\":50,\"time\":\"2016-10-28T23:44:52.144+09:00\",\"v\":0,\"error_id\":999,\"err\":{\"name\":\"StandardError\",\"message\":\"some error\",\"stack\":\"main.rb:40:in `\u003cmain\u003e'\"}}\n```\n\n### log with a message, an exception and custom data\n\n```ruby\nbegin\n  1 / 0\nrescue =\u003e ex\n  logger.error('Caught error', ex, reason: 'zero spec')\nend\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":14607,\"level\":50,\"time\":\"2016-10-16T22:26:48.836+09:00\",\"v\":0,\"msg\":\"Caught error\",\"err\":{\"name\":\"ZeroDivisionError\",\"message\":\"divided by 0\",\"stack\":\"main.rb:18:in `/'\\n ...'\"},\"reason\":\"zero spec\"}\n```\n\n### logs with blocks\n\n```ruby\nlogger.info { 'Hello!' }\n\nlogger.debug do\n  ['User dump', { name: 'Taro', age: 15 }]\nend\n\nlogger.error do\n  ['Failed to fetch info', ex, { id: 10 }]\nend\n\nlogger.fatal { ex }\n\nlogger.fatal do\n  ['Unexpected', ex]\nend\n```\n\nTo specify more than one message, exception, and custom data, the block returns them as an array.\n\n### Adding custom fields to all logs\n\nThe fields of `with_fields` add to all logs as is.\n\n```ruby\nlogger.with_fields = { version: '1.1.0' }\nlogger.debug(user: { name: 'Taro', age: 19 })\nlogger.info('Hello!', user: { name: 'Jiro' }, version: '2.3')\n```\n\n```json\n{\"name\":\"test\",\"hostname\":\"mint\",\"pid\":30182,\"level\":20,\"time\":\"2017-07-22T20:52:12.332+09:00\",\"v\":0,\"version\":\"1.1.0\",\"msg\":\"No message\",\"user\":{\"name\":\"Taro\",\"age\":19}}\n{\"name\":\"test\",\"hostname\":\"mint\",\"pid\":30308,\"level\":30,\"time\":\"2017-07-22T20:53:54.314+09:00\",\"v\":0,\"version\":\"2.3\",\"user\":{\"name\":\"Jiro\"},\"msg\":\"Hello!\"}\n```\n\nIf any field of with_fields is specified in each log, the field is overridden.\nIf the field's type is *Array*, both with_field value and logging value are merged with `concat` and `uniq`.\n\nIf the field's type is *Hash*, then values are merged recursively.\n\n```ruby\nlogger.with_fields = { version: '1.1.0', user: { name: 'Taro' } }\nlogger.debug(user: { age: 19 })\n```\n\n```json\n{\"name\":\"test\",\"hostname\":\"mint\",\"pid\":30182,\"level\":20,\"time\":\"2017-07-22T20:52:12.332+09:00\",\"v\":0,\"version\":\"1.1.0\",\"msg\":\"No message\",\"user\":{\"name\":\"Taro\",\"age\":19}}\n```\n\n### Create a child logger\n\n`logger.child(with_fields)` creates a child logger of self. Its argument `with_fields` add to all logs the child logger outputs. A child logger can also create its child logger. If you pass a block to this method, the child logger will be yielded to it.\n\n```ruby\nlogger = Ougai::Logger.new(STDOUT)\nlogger.with_fields = { app: 'yourapp', tags: ['service'], kind: 'main' }\n\nchild_logger = logger.child({ tags: ['user'], kind: 'logic' })\nlogger.info('Created child logger')\n\nchild_logger.info('Created a user', name: 'Mike')\n\ngc_logger = child_logger.child({ kind: 'detail' })\nchild_logger.info('Created grand child logger')\n\ngc_logger.debug('something detail', age: 34, weight: 72)\n\ngc_logger.child({ mode: 'processed' }) do |gcc_logger|\n  gcc_logger.info('Great-grandchild logger that will be cleaned up on block exit.')\n\n  :some_return_value\nend\n\nchild_logger.sev_threshold = :error # alias of level\nchild_logger.info('This is not outputted')\ngc_logger.info('This is not outputted')\nchild_logger.error('This is outputted')\ngc_logger.error('This is outputted')\n\nchild_logger.level = :debug # does not work because the level is below parent one\nchild_logger.debug('This is not outputted')\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":8342,\"level\":30,\"time\":\"2017-08-01T22:07:20.400+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\"],\"kind\":\"main\",\"msg\":\"Created child logger\"}\n{\"name\":\"Mike\",\"hostname\":\"mint\",\"pid\":8342,\"level\":30,\"time\":\"2017-08-01T22:07:20.400+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"logic\",\"msg\":\"Created a user\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":8342,\"level\":30,\"time\":\"2017-08-01T22:07:20.400+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"logic\",\"msg\":\"Created grand child logger\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":8342,\"level\":20,\"time\":\"2017-08-01T22:07:20.400+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"detail\",\"age\":34,\"weight\":72,\"msg\":\"something detail\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":8342,\"level\":20,\"time\":\"2017-08-01T22:07:20.400+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"detail\",\"mode\":\"processed\",\"msg\":\"Great-grandchild logger that will be cleaned up on block exit.\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":4894,\"level\":50,\"time\":\"2017-08-01T22:07:20.401+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"logic\",\"msg\":\"This is outputed\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":4894,\"level\":50,\"time\":\"2017-08-01T22:07:20.401+09:00\",\"v\":0,\"app\":\"yourapp\",\"tags\":[\"service\",\"user\"],\"kind\":\"detail\",\"msg\":\"This is outputed\"}\n```\n\nIf any field exists in both parent log and child log, the parent value is overridden or merged by child value.\n\nIf the field's type is *Hash*, then values are merged recursively.\n\n### Hook before logging\n\nSetting `before_log` of logger or child an *lambda* with `data` field, a process can be run before log each output.\n\n* Adding variable data (like Thread ID) to logging data can be defined in common.\n* Returning `false` in *lambda*, the log is cancelled and does not output.\n* The *before_log* of child logger is run ahead of the parent logger's.\n\n```ruby\nlogger.before_log = lambda do |data|\n  data[:thread_id] = Thread.current.object_id.to_s(36)\nend\n\nlogger.debug('on main thread')\nThread.new { logger.debug('on another thread') }\n```\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":13975,\"level\":20,\"time\":\"2017-08-06T15:35:53.435+09:00\",\"v\":0,\"msg\":\"on main thread\",\"thread_id\":\"gqe0ava6c\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":13975,\"level\":20,\"time\":\"2017-08-06T15:35:53.435+09:00\",\"v\":0,\"msg\":\"on another thread\",\"thread_id\":\"gqe0cb14g\"}\n```\n\n### Using broadcast, log output plural targets\n\n`Ougai::Logger.broadcast` can be used to like `ActiveSupport::Logger.broadcast`.\n\n#### An example\n\nOriginal `logger` outputs STDOUT and `error_logger` outputs `./error.log`.\nEvery calling for `logger` is propagated to `error_logger`.\n\n```ruby\nlogger = Ougai::Logger.new(STDOUT)\nlogger.level = Logger::INFO\n\nerror_logger = Ougai::Logger.new('./error.log')\nerror_logger.level = Logger::ERROR\nlogger.extend Ougai::Logger.broadcast(error_logger)\n\nlogger.info('Hello!')\n\nlogger.error('Failed to do something.')\n\nlogger.level = Logger::WARN # error_logger level is also set WARN by propagation\nlogger.warn('Ignored something.')\n```\n\n##### STDOUT\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":24915,\"level\":30,\"time\":\"2017-08-16T17:23:42.415+09:00\",\"v\":0,\"msg\":\"Hello!\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":24915,\"level\":50,\"time\":\"2017-08-16T17:23:42.416+09:00\",\"v\":0,\"msg\":\"Failed to do something.\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":24915,\"level\":40,\"time\":\"2017-08-16T17:23:42.416+09:00\",\"v\":0,\"msg\":\"Ignored something.\"}\n```\n\n##### error.log\n\n```json\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":24915,\"level\":50,\"time\":\"2017-08-16T17:23:42.415+09:00\",\"v\":0,\"msg\":\"Failed to do something.\"}\n{\"name\":\"main\",\"hostname\":\"mint\",\"pid\":24915,\"level\":40,\"time\":\"2017-08-16T17:23:42.416+09:00\",\"v\":0,\"msg\":\"Ignored something.\"}\n```\n\n\n## View pretty logs with node-bunyan or pino\n\nInstall [bunyan](https://github.com/trentm/node-bunyan) or [pino](https://github.com/pinojs/pino) via npm\n\n```\n$ npm install -g bunyan\n```\n\nPipe a log file to command\n\n```\n$ cat output.log | bunyan\n[2016-10-16T22:26:48.835+09:00]  INFO: main/14607 on mint: Info message!\n[2016-10-16T22:26:48.836+09:00] DEBUG: main/14607 on mint: Debugging (data_id=1, data_flag=true)\n[2016-10-16T22:26:48.836+09:00] DEBUG: main/14607 on mint: Debug!\n    custom_data: {\n      \"id\": 1,\n      \"name\": \"something\"\n    }\n[2016-10-16T22:26:48.836+09:00] FATAL: main/14607 on mint: Unexpected!\n    main.rb:12:in `\u003cmain\u003e'\n[2016-10-16T22:26:48.836+09:00] ERROR: main/14607 on mint: Caught error (reason=\"z\n    main.rb:18:in `/'\n      main.rb:18:in `\u003cmain\u003e'\n```\n\nIf you use *Ougai::Formatters::Pino*, you can use command [pino](https://github.com/pinojs/pino) as well as [bunyan](https://github.com/trentm/node-bunyan).\n\n## Use human Readable formatter for console\n\nAdd amazing_print to Gemfile and `bundle`\n\n```ruby\ngem 'amazing_print'\n```\n\nSet *Ougai::Formatters::Readable* instance to `formatter` accessor\n\n```ruby\nrequire 'ougai'\n\nlogger = Ougai::Logger.new(STDOUT)\nlogger.formatter = Ougai::Formatters::Readable.new\n```\n\n### Screen result example\n\n![Screen Shot](https://github.com/tilfin/ougai/blob/images/ougai_readable_format.png?raw=true)\n\n\n## How to use with famous products, services and libraries\n\n- [Use as Rails logger](https://github.com/tilfin/ougai/wiki/Use-as-Rails-logger) and apply the request with [Lograge](https://github.com/roidrage/lograge)\n- [Customize Sidekiq logger](https://github.com/tilfin/ougai/wiki/Customize-Sidekiq-logger)\n- [Forward logs to Fluentd](https://github.com/tilfin/ougai/wiki/Forward-logs-to-Fluentd)\n- [Forward logs to Logentries](https://github.com/tilfin/ougai/wiki/Forward-logs-to-Logentries)\n- [Use as ServerEngine logger](https://github.com/tilfin/ougai/wiki/Use-as-ServerEngine-logger)\n- [Forward logs to Loggly](https://github.com/tilfin/ougai/wiki/Forward-logs-to-Loggly)\n- [Use as Rack logger](https://github.com/tilfin/ougai/wiki/Use-as-Rack-logger)\n\n## Custom formatters and integrations\n- [Awesome Ougai](https://github.com/tilfin/ougai/wiki/Awesom-Ougai)\n\n## License\n\n[MIT](LICENSE.txt)\n","funding_links":[],"categories":["Logging","Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftilfin%2Fougai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftilfin%2Fougai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftilfin%2Fougai/lists"}