{"id":37207671,"url":"https://github.com/comerc/spylog","last_synced_at":"2026-01-14T23:52:47.090Z","repository":{"id":294946230,"uuid":"988561116","full_name":"comerc/spylog","owner":"comerc","description":"slog message spy utility","archived":false,"fork":false,"pushed_at":"2025-06-07T06:44:09.000Z","size":11,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-07T07:18:31.294Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/comerc.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,"zenodo":null}},"created_at":"2025-05-22T18:21:01.000Z","updated_at":"2025-06-07T06:44:13.000Z","dependencies_parsed_at":"2025-05-29T09:16:06.114Z","dependency_job_id":null,"html_url":"https://github.com/comerc/spylog","commit_stats":null,"previous_names":["comerc/spylog"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/comerc/spylog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comerc%2Fspylog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comerc%2Fspylog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comerc%2Fspylog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comerc%2Fspylog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/comerc","download_url":"https://codeload.github.com/comerc/spylog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comerc%2Fspylog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28439574,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"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":[],"created_at":"2026-01-14T23:52:46.462Z","updated_at":"2026-01-14T23:52:47.084Z","avatar_url":"https://github.com/comerc.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Прощай error-hell: альтернативная обработка ошибок\n\n![](https://habrastorage.org/webt/os/mi/cg/osmicg2qjlfyz5spftc0dy_4qvk.jpeg)\n\nВ эпоху становления асинхронного программирования JavaScript-разработчики столкнулись с явлением, получившим название \"callback-hell\" — бесконечной вложенностью функций обратного вызова. Хотя с точки зрения функционального программирования функции являются полноправными гражданами первого класса, принцип \"всё хорошо в меру\" никто не отменял. Появление Promise и механизма async/await стало спасительным решением этой проблемы.\n\nВ мире Go у нас есть более элегантные инструменты — каналы и горутины. Однако совершенству нет предела, и здесь нас поджидает другая ловушка — \"error-hell\". Новички в Golang часто приходят в недоумение от того, что идиомы языка требуют обработки ошибок буквально на каждом шагу.\n\n## Двойственность\n\nУ такой педантичности есть неоспоримые преимущества для библиотек общего назначения:\n\n1. **Локальность обработки** — ошибку проще обработать в месте её возникновения\n2. **Тестируемость** — покрытие тестами становится более удобным и предсказуемым\n\nОднако в прикладных программах мы получаем существенное зашумление кода. Передача ошибок вверх по стеку вызовов превращается в \"чемодан без ручки\" — и тащить тяжело, и выбросить жалко.\n\nКак следствие, в больших проектах на каждом уровне обработки ошибки, по принципу разделения ответственности, может быть добавлена новая запись в лог. В довесок получаем замусоривание логов.\n\n## Альтернатива\n\nЧто если пересмотреть эту практику? Представим себе мир, где мы логируем ошибки в месте их первоначального появления, а передаём наверх только тогда, когда это действительно необходимо для ветвления логики программы.\n\nНо тут возникает закономерный вопрос: как тестировать такой код? Вместо проверки возвращённой ошибки нам нужен способ убедиться, что логирование действительно произошло.\n\n## Структурированное логирование\n\nБлагодаря механизму [структурированного логирования в Go с помощью Slog](https://habr.com/ru/companies/slurm/articles/798207/), записи в лог приобретают формализованную структуру. Это позволяет задавать и выполнять проверки необходимых значений в тестах.\n\nБиблиотека [comerc/spylog](https://github.com/comerc/spylog) и её [аналоги](https://github.com/palkan/slog-spy) элегантно решают задачу перехвата вывода в лог для целей тестирования.\n\n## Практическое применение\n\n```go\nimport (\n\t\"log/slog\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype SomeObject struct {\n\tlog *slog.Logger // определяем инстанс лога для модуля\n\tval any\n}\n\nfunc NewSomeObject(val any) *SomeObject {\n\treturn \u0026SomeObject{\n\t\tlog: slog.With(\"module\", \"module_name\"), // задаём название модуля для логирования\n\t\tval: val,\n\t}\n}\n\nfunc (o *SomeObject) SomeMethod() {\n\t// при возникновении ошибки, записываем данные в лог \n\to.log.Error(\"test message from some method\", \n    \"attr_key1\", \"attr_val1\",\n    \"attr_key2\", \"attr_val2\",\n  ) \n}\n\nfunc TestSomeMethod(t *testing.T) {\n\tvar o *SomeObject\n\tlogHandler := GetModuleLogHandler(\"module_name\", t.Name(), func() {\n\t\to = NewSomeObject(\"val\") // вызываем функцию-конструктор в обёртке logHandler\n\t})\n\to.SomeMethod() // вызываем тестируемый метод\n\n\tslog.Error(\"test message from default\") // другие записи в лог не перехватываются\n\n\trequire.True(t, len(logHandler.Records) == 1)\n\tr0 := logHandler.Records[0]\n\n\tassert.Equal(t, \"test message from some method\", r0.Message)\n\tassert.Equal(t, \"attr_val1\", GetAttrValue(r0, \"attr_key1\"))\n\tassert.Equal(t, \"attr_val2\", GetAttrValue(r0, \"attr_key2\"))\n}\n```\n\n## Заключение\n\nДанный подход освобождает нас от необходимости совершать грех \"shadowed error\" или явно игнорировать ошибки. Обработка ошибок по месту их возникновения может значительно облегчить разработку на Go, если руководствоваться здравым смыслом и проводить аналогии с решением callback-hell в JavaScript.\n\nОднако важно помнить, что этот подход требует осознанного применения и может не подходить для всех сценариев использования. Ключ к успеху — в разумном балансе между упрощением кода и сохранением контроля над потоком выполнения программы.\n\n---\n\n## Анализ от Claude\n\nПредложенный подход представляет интересную альтернативу классической обработке ошибок в Go, но требует осторожного применения:\n\n### Сильные стороны\n- Убираем шаблонный `if err != nil` и необходимость передачи ошибок вверх по стеку\n- Логирование в месте возникновения ошибки может быть очень полезным для отладки\n- Элегантное решение для тестирования через перехват логов\n\n### Потенциальные риски\n\n- **Потеря семантики** — вызывающий код теряет информацию о том, что операция завершилась неудачно\n- **Нарушение контракта** — функция может \"молча\" провалиться, что противоречит принципу явности в Go\n- **Неопределенность состояния** — функция может завершиться \"успешно\", но с внутренними ошибками\n- **Сложность композиции** — труднее строить сложную логику, когда неясно, какие операции успешны\n- **Сложность отладки** — без явного возврата ошибок труднее отследить цепочку проблем и путь их распространения\n\n### Рекомендации по применению\n- Подходит для утилитарных функций, где ошибка не влияет на основной поток\n- Хорошо работает в сценариях \"best effort\" (например, метрики, аналитика)\n- Не рекомендуется для критически важных операций (работа с БД, файлами, сетью)\n\n---\n\n[опубликовано](https://habr.com/ru/articles/912150/)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomerc%2Fspylog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcomerc%2Fspylog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomerc%2Fspylog/lists"}