{"id":13413336,"url":"https://github.com/xybor-x/xylog","last_synced_at":"2026-01-25T16:15:55.118Z","repository":{"id":58552488,"uuid":"531370698","full_name":"xybor-x/xylog","owner":"xybor-x","description":"Python-like logging design in Golang","archived":false,"fork":false,"pushed_at":"2023-04-24T13:25:46.000Z","size":172,"stargazers_count":16,"open_issues_count":4,"forks_count":3,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2024-09-23T06:07:26.939Z","etag":null,"topics":["go","golang","golang-library","jsonlogging","log","logger","logging","logging-library","performance","python-like","structurelogging"],"latest_commit_sha":null,"homepage":"","language":"Go","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/xybor-x.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2022-09-01T05:06:08.000Z","updated_at":"2024-04-30T16:39:40.000Z","dependencies_parsed_at":"2024-06-19T01:42:13.873Z","dependency_job_id":null,"html_url":"https://github.com/xybor-x/xylog","commit_stats":{"total_commits":33,"total_committers":2,"mean_commits":16.5,"dds":0.09090909090909094,"last_synced_commit":"132e2922783651afb7c88c16e1ffdceb04b7a0ae"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/xybor-x/xylog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xybor-x%2Fxylog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xybor-x%2Fxylog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xybor-x%2Fxylog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xybor-x%2Fxylog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xybor-x","download_url":"https://codeload.github.com/xybor-x/xylog/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xybor-x%2Fxylog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28755125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T13:59:49.818Z","status":"ssl_error","status_checked_at":"2026-01-25T13:59:33.728Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["go","golang","golang-library","jsonlogging","log","logger","logging","logging-library","performance","python-like","structurelogging"],"created_at":"2024-07-30T20:01:38.065Z","updated_at":"2026-01-25T16:15:55.099Z","avatar_url":"https://github.com/xybor-x.png","language":"Go","funding_links":[],"categories":["Logging","日志记录"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"readme":"[![xybor founder](https://img.shields.io/badge/xybor-huykingsofm-red)](https://github.com/huykingsofm)\n[![Go Reference](https://pkg.go.dev/badge/github.com/xybor-x/xylog.svg)](https://pkg.go.dev/github.com/xybor-x/xylog)\n[![GitHub Repo stars](https://img.shields.io/github/stars/xybor-x/xylog?color=yellow)](https://github.com/xybor-x/xylog)\n[![GitHub top language](https://img.shields.io/github/languages/top/xybor-x/xylog?color=lightblue)](https://go.dev/)\n[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/xybor-x/xylog)](https://go.dev/blog/go1.18)\n[![GitHub release (release name instead of tag name)](https://img.shields.io/github/v/release/xybor-x/xylog?include_prereleases)](https://github.com/xybor-x/xylog/releases/latest)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/e9146e2ccfd745a48a42fc723918d4cb)](https://www.codacy.com/gh/xybor-x/xylog/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=xybor-x/xylog\u0026utm_campaign=Badge_Grade)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/e9146e2ccfd745a48a42fc723918d4cb)](https://www.codacy.com/gh/xybor-x/xylog/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=xybor-x/xylog\u0026utm_campaign=Badge_Coverage)\n[![Go Report](https://goreportcard.com/badge/github.com/xybor-x/xylog)](https://goreportcard.com/report/github.com/xybor-x/xylog)\n\n# Introduction\n\nPackage xylog is designed for [leveled](#logging-level) and\n[structured](#structured-logging) logging, [dynamic fields](#macros),\n[high performance](#benchmark), [zone management](#hierarchical-logger), simple\nconfiguration, and readable syntax.\n\nThe library is combined by\n[python logging](https://docs.python.org/3/library/logging.html) design and\n[zap](https://github.com/uber-go/zap) encoding approach.\n\n# Quick start\n\nYou can easily configure a logger with `SimpleConfig`.\n\nThere are some fields you can modify with this way (note that all fields are\noptional):\n\n-   `Name` is the name of Logger. It can be used later with `GetLogger`\n    function. Default to an empty name (the root logger).\n\n-   `Encoding` to format the output. Default to `TextEncoding`.\n\n-   `Filename` specifies that `Logger` will write the output to a file. Do NOT\n    use together with `Writer`.\n\n-   `Filemode` specifies the mode to open file. Default to `APPEND` \\| `CREATE`\n    \\| `WRONLY`.\n\n-   `Fileperm` specifies the permission when creating the file. Default to 0666.\n\n-   `Level` specifies the logging level. Default to `WARNING`.\n\n-   `TimeLayout` when format the time string. Default to `RFC3339Nano`.\n\n-   `Writer` specifies that Logger will write the output to a file. Do NOT use\n    together with `Filename`.\n\n```golang\nvar config = \u0026xylog.SimpleConfig{\n    Name:   \"simple-logger\",\n    Level:  xylog.DEBUG,\n    Writer: os.Stdout,\n}\n\nvar logger, err = config.AddMacro(\"level\", \"levelname\").Apply()\nif err != nil {\n    fmt.Println(\"An error occurred:\", err)\n    os.Exit(1)\n}\ndefer xylog.Flush()\n\nlogger.Debug(\"logging message\")\nlogger.Event(\"create-user\").Field(\"username\", \"foo\").\n    Field(\"email\", \"bar@buzz.com\").Field(\"Age\", 25).Info()\n\n// Output:\n// level=DEBUG messsage=\"logging message\"\n// level=INFO event=create-user username=foo email=bar@buzz.com Age=25\n```\n\n# Full configuration\n\n`Logger` is directly used by application code. `Logger` names are dot-separated\nhierarchical names, such as \"a\", \"a.b\", \"a.b.c\" or similar. For \"a.b.c\", its\nparents are \"a\" and \"a.b\".\n\nA `Logger` is obtained using `GetLogger` method. If the `Logger` with that name\ndidn't exist before, the method will create a new one. The `Logger` with empty\nname is the root one.\n\n```golang\nvar logger = xylog.GetLogger(\"example\")\ndefer xylog.Flush()\n```\n\n`Handler` is responsible for generating logging messages. Like `Logger`,\n`Handler` is also identified by its name, however, the name is not hierarchical.\nEvery `GetHandler` call with the same name gives the same `Handler`.\n\n_Exception: `Handlers` with the empty names are always different._\n\n```golang\nvar handler = xylog.GetHandler(\"handler\")\n```\n\n`Emitter` writes logging messages to the specified output. Currently, only\n`StreamEmitter` is supported. You can use any `Writer` in this `Emitter` type.\n\n```golang\nvar emitter = xylog.NewStreamEmitter(os.Stdout)\n```\n\nWhen a logging method is called, the `Logger` creates a `LogRecord` and sends it\nto underlying `Handlers`. `Handlers` convert `LogRecord` to text and send it\nto `Emitters`.\n\nA `Logger` can have multiple `Handlers`, and a `Handler` can have multiple\n`Emitters`.\n\n```golang\nhandler.AddEmitter(emitter)\nlogger.AddHandler(handler)\n```\n\nAfter preparing `Logger`, `Handler`, and `Emitter`, you can log the first\nmessages.\n\n```golang\nlogger.Debug(\"foo\") // This message is blocked by Logger's preferred level.\nlogger.Warning(\"bar\")\n\n// Output:\n// message=bar\n```\n\n# Logging level\n\nBoth `Logger` and `Handler` has its own preferred level. If a logging level is\nlower than the preferred one, the message will not be logged.\n\nBy default:\n\n-   `Handler`'s preferred level is `NOTSET` (it logs all logging levels).\n-   `Logger`'s preferred level depends on its parents. When a `Logger` is newly\n    created, its preferred level is the nearest parent's one. The root logger's\n    preferred level is `WARNING`.\n\nYou can set a new preferred level for both `Logger` and `Handler`.\n\n```golang\nlogger.SetLevel(xylog.DEBUG)\n\nlogger.Debug(\"foo\")\nlogger.Warning(\"bar\")\n\n// Output:\n// message=foo\n// message=bar\n```\n\nIn the following example, however, the first message with DEBUG can bypass the\n`Logger`, but will be prevented by `Handler`.\n\n```golang\nlogger.SetLevel(xylog.DEBUG)\nhandler.SetLevel(xylog.INFO)\n\nlogger.Debug(\"foo\")\nlogger.Warning(\"bar\")\n\n// Output:\n// message=bar\n```\n\nThe numeric values of logging levels are given in the following table. If you\ndefine a level with the same numeric value, it overwrites the predefined value.\n\n| Level        | Numeric value |\n| ------------ | ------------- |\n| CRITICAL     | 50            |\n| ERROR/FATAL  | 40            |\n| WARN/WARNING | 30            |\n| INFO         | 20            |\n| DEBUG        | 10            |\n| NOTSET       | 0             |\n\n# Structured logging\n\nIf the logging message has more than one field, `EventLogger` can help.\n\n```golang\nlogger.Event(\"add-user\").Field(\"name\", \"david\").Field(\"email\", \"david@dad.com\").Info()\n\n// Output:\n// event=add-user name=david email=david@dad.com\n```\n\nYou also add a field to `Logger` or `Handler` permanently. All logging messages\nwill always include permanent fields.\n\n```golang\nlogger.AddField(\"host\", \"localhost\")\nhandler.AddField(\"port\", 3333)\n\nlogger.Info(\"start server\")\n\n// Output:\n// host=localhost port=3333 message=\"start server\"\n```\n\n_NOTE: Fixed fields added to `Handler` will log faster than the one added to `Logger`_\n\n`Handler` can support different encoding types. By default, it is\n`TextEncoding`.\n\nYou can log the message with JSON format too.\n\n```golang\nimport \"github.com/xybor-x/xylog/encoding\"\n\nhandler.SetEncoding(encoding.NewJSONEncoding())\n\nlogger.Warning(\"this is a message\")\nlogger.Event(\"failed\").Field(\"id\", 1).Error()\n\n// Output:\n// {\"message\":\"this is a message\"}\n// {\"event\":\"failed\",\"id\": 1}\n```\n\n# Macros\n\nYou can log special fields whose values change every time you log. These fields\ncalled macros.\n\nOnly the `Handler` can add macros.\n\n```golang\nhandler.AddMacro(\"level\", \"levelname\")\n\nlogger.Warning(\"this is a warning message\")\n\n// Output:\n// level=WARNING message=\"this is a warning message\"\n```\n\nThe following table shows supported macros.\n\n| MACRO             | DESCRIPTION                                                                                             |\n| ----------------- | ------------------------------------------------------------------------------------------------------- |\n| `asctime`         | Textual time when the LogRecord was created.                                                            |\n| `created`         | Time when the LogRecord was created (time.Now().Unix() return value).                                   |\n| `filename`\\*      | Filename portion of pathname.                                                                           |\n| `funcname`\\*      | Function name logged the record.                                                                        |\n| `levelname`       | Text logging level for the message (\"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\", \"CRITICAL\").                   |\n| `levelno`         | Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).                          |\n| `lineno`\\*        | Source line number where the logging call was issued.                                                   |\n| `module`\\*        | The module called log method.                                                                           |\n| `msecs`           | Millisecond portion of the creation time.                                                               |\n| `name`            | Name of the logger.                                                                                     |\n| `pathname`        | Full pathname of the source file where the logging call was issued.                                     |\n| `process`         | Process ID.                                                                                             |\n| `relativeCreated` | Time in milliseconds between the time LogRecord was created and the time the logging module was loaded. |\n\n_\\* These are macros that are only available if `xylog.SetFindCaller` is called with `true`._\n\n# Filter\n\n`Filter` can be used by `Handlers` and `Loggers` for more sophisticated\nfiltering than is provided by levels.\n\nA `Filter` instance needs to define `Filter(LogRecord)` method, which returns\n`true` if it allows logging the `LogRecord`, and vice versa.\n\n```golang\ntype NameFilter struct {\n    name string\n}\n\nfunc (f *NameFilter) Filter(record xylog.LogRecord) bool {\n    return f.name == record.Name\n}\n\nhandler.AddFilter(\u0026NameFilter{\"example.user\"})\n\nvar userLogger = xylog.GetLogger(\"example.user\")\nvar serviceLogger = xylog.GetLogger(\"example.service\")\n\nuserLogger.Warning(\"this is the user logger\")\nserviceLogger.Warning(\"this is the service logger\")\n\n// Output:\n// message=\"this is the user logger\"\n```\n\n# Hierarchical logger\n\nAs the first section mentioned, the `Logger`'s name is hierarchical. With this\nfeature, you can setup a common `Logger` with a specified configuration and uses\nin different application zones.\n\n```golang\n// common/setup.go\nfunc init() {\n    var emitter = xylog.NewStreamEmitter(os.Stderr)\n    var handler = xylog.GetHandler(\"\")\n    handler.AddEmitter(emitter)\n    handler.SetEncoding(encoding.NewJSONEncoding())\n    handler.AddMacro(\"time\", \"asctime\")\n    handler.AddMacro(\"level\", \"levelname\")\n\n    var logger = xylog.GetLogger(\"parent\")\n    logger.AddHandler(handler)\n    logger.SetLevel(xylog.WARNING)\n}\n```\n\n```golang\n// user/foo.go\nimport _ \"common\"\n\nvar logger = xylog.GetLogger(\"parent.user\")\ndefer xylog.Flush()\nlogger.SetLevel(xylog.INFO)\nlogger.AddField(\"module\", \"user\")\n\nlogger.Info(\"this is user module\")\nlogger.Debug(\"this is a not logged message\")\n\n// Output:\n// time=[time] level=INFO module=user message=\"this is user module\"\n```\n\n```golang\n// service/bar.go\nimport _ \"common\"\n\nvar logger = xylog.GetLogger(\"parent.service\")\ndefer xylog.Flush()\nlogger.AddField(\"module\", \"service\")\nlogger.AddField(\"service\", \"bar\")\n\nlogger.Warning(\"this is service module\")\n\n// Output:\n// time=[time] level=INFO module=service service=bar message=\"this is service module\"\n```\n\n# Benchmark\n\nCPU: AMD Ryzen 7 5800H (3.2Ghz)\n\nThese benchmark of xylog are measured with `SetFindCaller` is called with\n`false`.\n\n_NOTE: The benchmarks are run on a different CPU from the [origin](https://github.com/uber-go/zap/blob/master/README.md#performance), so the benchmark values may be different too._\n\nLog a message and 10 fields:\n\n| Package             |        Time | Time % to zap | Objects Allocated |\n| :------------------ | ----------: | ------------: | ----------------: |\n| :zap: zap           |  1707 ns/op |           +0% |       5 allocs/op |\n| :zap: zap (sugared) |  2043 ns/op |          +20% |      10 allocs/op |\n| zerolog             |   884 ns/op |          -48% |       1 allocs/op |\n| go-kit              |  6255 ns/op |         +266% |      58 allocs/op |\n| logrus              |  8384 ns/op |         +391% |      80 allocs/op |\n| apex/log            | 22707 ns/op |        +1230% |      65 allocs/op |\n| log15               | 25461 ns/op |        +1391% |      75 allocs/op |\n| :rocket: xylog      |  3518 ns/op |         +106% |      77 allocs/op |\n\nLog a message with a logger that already has 10 fields of context:\n\n| Package             |        Time | Time % to zap | Objects Allocated |\n| :------------------ | ----------: | ------------: | ----------------: |\n| :zap: zap           |   140 ns/op |           +0% |       0 allocs/op |\n| :zap: zap (sugared) |   181 ns/op |          +29% |       1 allocs/op |\n| zerolog             |    89 ns/op |          -36% |       0 allocs/op |\n| go-kit              |  5963 ns/op |        +4159% |      57 allocs/op |\n| logrus              |  6590 ns/op |        +4607% |      69 allocs/op |\n| apex/log            | 21777 ns/op |       +15455% |      54 allocs/op |\n| log15               | 15124 ns/op |       +10702% |      71 allocs/op |\n| :rocket: xylog      |   416 ns/op |         +197% |       6 allocs/op |\n\nLog a static string, without any context or `printf`-style templating:\n\n| Package             |       Time | Time % to zap | Objects Allocated |\n| :------------------ | ---------: | ------------: | ----------------: |\n| :zap: zap           |  154 ns/op |           +0% |       0 allocs/op |\n| :zap: zap (sugared) |  195 ns/op |          +27% |       1 allocs/op |\n| zerolog             |   87 ns/op |          -44% |       0 allocs/op |\n| go-kit              |  382 ns/op |         +148% |      10 allocs/op |\n| logrus              | 1008 ns/op |         +554% |      24 allocs/op |\n| apex/log            | 1744 ns/op |        +1032% |       6 allocs/op |\n| log15               | 4246 ns/op |        +2657% |      21 allocs/op |\n| :rocket: xylog      |  447 ns/op |         +190% |       6 allocs/op |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxybor-x%2Fxylog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxybor-x%2Fxylog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxybor-x%2Fxylog/lists"}