Suprime envio de erros por email e adiciona registro opcional de logs no OpenSearch (com infra de dev e índices por ambiente)#1432
Conversation
|
@copilot se o problema é a forma de registro de log, então prepara o sistema para registrar os logs no OpenSearch ou no console caso o Opensearch não esteja ativado |
Agent-Logs-Url: https://github.com/scieloorg/core/sessions/0ba82b8e-4597-4653-a220-5c75a0e8d446 Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Agent-Logs-Url: https://github.com/scieloorg/core/sessions/ece7f077-1899-4acb-8ecb-28cde2f8e6fe Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Feito em 4dda20f. Adicionei um handler de logging para OpenSearch ( |
|
Adicione em ambiente de desenvolvimento a infra estrutura para que seja possível testar os logs no OpenSearch. |
| ) | ||
| OPENSEARCH_LOGGING_LEVEL = env.str("OPENSEARCH_LOGGING_LEVEL", default="INFO") | ||
| OPENSEARCH_LOGGING_ENVIRONMENT = env.str( | ||
| "OPENSEARCH_LOGGING_ENVIRONMENT", default="development" |
There was a problem hiding this comment.
O valor padrão deve ser prod para produção e dev para desenvolvimento.
There was a problem hiding this comment.
Mesmo com índice separado por ambiente mantenha uma semântica no dado: OPENSEARCH_LOGGING_ENVIRONMENT
There was a problem hiding this comment.
@copilot Adicione em ambiente de desenvolvimento a infra estrutura para que seja possível testar os logs no OpenSearch. O valor padrão deve ser prod para produção e dev para desenvolvimento. Mesmo com índice separado por ambiente mantenha uma semântica no dado: OPENSEARCH_LOGGING_ENVIRONMENT
There was a problem hiding this comment.
Feito em 1bc0532. Defaults aplicados: OPENSEARCH_LOGGING_ENVIRONMENT=prod em production.py, dev em base.py/local.py (overridáveis via env). O valor é mantido como campo top-level (environment) em todo documento indexado E também é usado para compor o nome do índice (ver thread sobre índices separados). Para testar localmente, adicionei os serviços opensearch e opensearch-dashboards no local.yml (single-node, sem TLS, dashboards em http://localhost:5609). Basta ligar USE_OPENSEARCH_LOGGING=yes em .envs/.local/.django.
There was a problem hiding this comment.
Feito em 1bc0532. Mesmo com índices separados, o campo environment (vindo de OPENSEARCH_LOGGING_ENVIRONMENT) é mergeado em todo documento via extra_fields do handler, preservando a semântica dentro do dado.
| "http_auth": _opensearch_http_auth, | ||
| "use_ssl": OPENSEARCH_LOGGING_USE_SSL, | ||
| "verify_certs": OPENSEARCH_LOGGING_VERIFY_CERTS, | ||
| "extra_fields": { |
There was a problem hiding this comment.
Adiciona exemplo de valores extras para que passa ser utilizado em código. Isso é muito importante para que possamos adicionar valores importantes para os logs.
There was a problem hiding this comment.
@copilot Adiciona exemplo de valores extras para que passa ser utilizado em código.
There was a problem hiding this comment.
Feito em 1bc0532. O OpenSearchLogHandler agora promove qualquer chave passada em extra={...} para campo top-level no documento (filtrável nos dashboards). Exemplos completos foram adicionados ao docstring de config/logging_handlers.py — incluindo mensagem simples, interpolação posicional, campos contextuais via extra= (user_id, request_id, article_pid, duration_ms, etc.) e captura de exceções com logger.exception(..., extra={...}). Comentário também adicionado em base.py no bloco extra_fields apontando para esse uso.
There was a problem hiding this comment.
Já implementado em 1bc0532. Exemplos de uso de extra={...} (campos contextuais como user_id, request_id, article_pid, duration_ms) estão no docstring de config/logging_handlers.py (seção "Usage from application code"). O handler promove automaticamente as chaves de extra= para campos top-level no documento OpenSearch — ou seja, basta logger.info("imported article", extra={"user_id": user.id, "article_pid": pid}) para que esses valores fiquem filtráveis nos dashboards.
| USE_OPENSEARCH_LOGGING = env.bool("USE_OPENSEARCH_LOGGING", default=False) | ||
| OPENSEARCH_LOGGING_HOSTS = env.list("OPENSEARCH_LOGGING_HOSTS", default=[]) | ||
| OPENSEARCH_LOGGING_INDEX = env.str( | ||
| "OPENSEARCH_LOGGING_INDEX", default="scielo-core-logs" |
There was a problem hiding this comment.
Sugiro que seja melhor criar índices separados por ambiente.
Exemplo
core-logs-prod
core-logs-hml
There was a problem hiding this comment.
@copilot Sugiro que seja melhor criar índices separados por ambiente.
There was a problem hiding this comment.
Feito em 1bc0532. O nome do índice agora é composto automaticamente como {OPENSEARCH_LOGGING_INDEX_BASE}-{OPENSEARCH_LOGGING_ENVIRONMENT} (com sufixo de data opcional via OPENSEARCH_LOGGING_INDEX_DATE_FORMAT). Exemplos resultantes: core-logs-prod-2026.04.28, core-logs-hml-2026.04.28, core-logs-dev-2026.04.28. OPENSEARCH_LOGGING_INDEX_BASE tem default core-logs e ainda há a variável OPENSEARCH_LOGGING_INDEX como escape hatch caso seja necessário definir um nome arbitrário.
There was a problem hiding this comment.
Já implementado em 1bc0532. O nome do índice é composto automaticamente como {OPENSEARCH_LOGGING_INDEX_BASE}-{OPENSEARCH_LOGGING_ENVIRONMENT} (default base = core-logs), gerando core-logs-prod, core-logs-hml, core-logs-dev etc., com sufixo de data adicionado em cima (core-logs-prod-2026.04.28). Definido em config/settings/base.py e overridado por ambiente em production.py (prod) e local.py (dev).
There was a problem hiding this comment.
Já implementado em 1bc0532. O nome do índice é composto automaticamente como {OPENSEARCH_LOGGING_INDEX_BASE}-{OPENSEARCH_LOGGING_ENVIRONMENT} (default base = core-logs), gerando core-logs-prod, core-logs-hml, core-logs-dev, com sufixo de data adicionado em cima (ex.: core-logs-prod-2026.04.28). OPENSEARCH_LOGGING_ENVIRONMENT defaulta para prod em production.py e dev em base.py/local.py, sempre overridável via env var. Há também OPENSEARCH_LOGGING_INDEX como escape hatch para nomes arbitrários.
| @@ -0,0 +1,214 @@ | |||
| """ | |||
There was a problem hiding this comment.
Adicione exemplos de como utilizar essa classe para registra logs no código.
There was a problem hiding this comment.
@copilot Adicione exemplos de como utilizar essa classe para registra logs no código.
There was a problem hiding this comment.
Feito em 1bc0532. Adicionei uma seção "Usage from application code" no docstring de config/logging_handlers.py com exemplos cobrindo: mensagem simples (logger.info(...)), interpolação com argumentos (logger.warning("took %.2fs", x)), campos estruturados via extra={"user_id": ..., "request_id": ...} (promovidos automaticamente para top-level no documento OpenSearch), e captura de exceções com logger.exception(..., extra={...}).
There was a problem hiding this comment.
Já implementado em 1bc0532. Veja o docstring do módulo config/logging_handlers.py — seção "Usage from application code" cobre: mensagem simples (logger.info(...)), interpolação posicional (logger.warning("took %.2fs on %s", x, table)), campos estruturados via extra={"user_id": ..., "request_id": ...} (promovidos automaticamente para top-level), e captura de exceções com logger.exception("...", extra={...}).
| "formatter": "simple", | ||
| "encoding": "utf-8", | ||
| }, | ||
| # Override Django's default AdminEmailHandler (registered in |
There was a problem hiding this comment.
Adicione a mesma infraestrutura de logs para o arquivos de desenvolvimento, porém com uma flag para alterar entre utilizar o handler console e OpenSearchLogHandler
There was a problem hiding this comment.
@copilot Adicione a mesma infraestrutura de logs para o arquivos de desenvolvimento, porém com uma flag para alterar entre utilizar o handler console e OpenSearchLogHandler
There was a problem hiding this comment.
Feito em 1bc0532. Em config/settings/local.py agora há um bloco "OpenSearch logging (development)" que faz override do LOGGING herdado de base.py com defaults para o serviço opensearch do local.yml (host http://opensearch:9200, sem SSL, sem verificação de certs, env dev, índice core-logs-dev). A flag para alternar entre console apenas e console + OpenSearchLogHandler é a mesma já existente, USE_OPENSEARCH_LOGGING (definida em .envs/.local/.django como no por padrão para não mudar o comportamento atual).
There was a problem hiding this comment.
Já implementado em 1bc0532. Em config/settings/local.py há o bloco "OpenSearch logging (development)" que aplica defaults para o serviço opensearch do local.yml (host http://opensearch:9200, sem SSL, env dev, índice core-logs-dev). A flag para alternar entre apenas console e console + OpenSearchLogHandler é a USE_OPENSEARCH_LOGGING em .envs/.local/.django (default no = só console; yes = console + OpenSearch).
Agent-Logs-Url: https://github.com/scieloorg/core/sessions/bd487e74-79d9-4b8e-9589-a7bd31e89160 Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
Feito em 1bc0532. Adicionei serviços Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Já implementado. O handler
Já implementado em 1bc0532. Em |
There was a problem hiding this comment.
Pull request overview
This PR updates the Django logging setup to suppress error emails and optionally ship structured logs to OpenSearch, with environment-specific index naming so logs from dev/hml/prod stay separated.
Changes:
- Add an
OpenSearchLogHandler(async, queue-backed) and wire it into DjangoLOGGINGas an optional sink. - Compose
OPENSEARCH_LOGGING_INDEXfrom a base + environment, with production/local overrides. - Add local dev OpenSearch/OpenSearch Dashboards services and document the relevant env vars.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| requirements/base.txt | Adds opensearch-py dependency used by the optional logging handler. |
| local.yml | Adds OpenSearch + Dashboards services and a persistent volume for local development. |
| config/settings/base.py | Introduces OpenSearch logging settings, composes index names, and disables admin error emails via a null mail_admins handler. |
| config/settings/local.py | Applies dev defaults (HTTP/no SSL, default host) and conditionally attaches the OpenSearch handler based on USE_OPENSEARCH_LOGGING. |
| config/settings/production.py | Sets production defaults for environment/index and updates production/Sentry logging handler sets accordingly. |
| config/logging_handlers.py | New OpenSearch logging handler implementation with structured extra={...} promotion. |
| .envs/.local/.django | Documents local env toggles for enabling OpenSearch logging and Dashboards access. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Optional mapping merged into every log document (useful for | ||
| environment / service tags). | ||
| queue_size: | ||
| Maximum number of buffered records. Older records are dropped |
There was a problem hiding this comment.
The docstring says that when the queue is full "Older records are dropped", but emit() currently drops the new record (put_nowait raises queue.Full and the record is discarded). Please either update the documentation to reflect the real behavior or change the queueing logic to evict the oldest record(s) before enqueuing the new one.
| Maximum number of buffered records. Older records are dropped | |
| Maximum number of buffered records. New records are dropped |
| "root": {"level": "INFO", "handlers": _prod_default_handlers}, | ||
| "loggers": { | ||
| "django.db.backends": { | ||
| "level": "ERROR", | ||
| "handlers": ["console"], | ||
| "handlers": _prod_default_handlers, | ||
| "propagate": False, |
There was a problem hiding this comment.
In the USE_SENTRY logging configuration, the loggers section does not define the django (and/or django.request) logger, even though the comments/intent here are to override Django’s default email handler and to attach the same handler set used by root. With disable_existing_loggers=True, this can result in Django loggers being disabled and not propagating to root, which would break both console/OpenSearch logging and the intended mail suppression. Consider either setting disable_existing_loggers=False (as in base.py) or explicitly adding django/django.request entries wired to _prod_default_handlers.
…pensearchpy para somente Warning.
…rting Copilot/fix email error reporting
|
@robertatakenaka PR revisado e testado localmente e funcionando como esperado. |
A aplicação enviava emails para
ADMINSa cada erro (ex.: 500 não tratado) mesmo sem nenhummail_adminsconfigurado no projeto. A causa é oDEFAULT_LOGGINGdo Django (django.utils.log), que sempre é aplicado antes doLOGGINGdo projeto e anexa umAdminEmailHandlerao loggerdjango. ComDEBUG=FalseeADMINSpopulado, todo ERROR dedjango.requestvirava email. Além disso, atendendo ao feedback do revisor, o sistema agora pode enviar logs para um cluster OpenSearch quando configurado, com índices separados por ambiente, infraestrutura local pronta para teste, e cai de volta para o console caso o OpenSearch não esteja ativado.O que esse PR faz?
Desativa o
AdminEmailHandlerherdado doDEFAULT_LOGGINGdo Django nas três configurações deLOGGINGdo projeto (base.py,local.py,production.py— branchesUSE_SENTRY=True/False):mail_adminsporlogging.NullHandler.django. Esse passo é o crítico embase.py: comodisable_existing_loggers: Falsepreserva os handlers já anexados, sem essa redefinição a instância deAdminEmailHandlerregistrada peloDEFAULT_LOGGINGpermaneceria ativa.Adiciona um sink de logs opcional para OpenSearch, com fallback automático para o console:
config/logging_handlers.pycom a classeOpenSearchLogHandler:opensearch-py.Handler.handleError(nunca propagam), garantindo que logging jamais derrube a aplicação.strftime; documento inclui@timestamp,level,logger,message,module,func_name,line_no,process,thread,host,exception(quando presente) e campos extras configuráveis (service,environment).extra={...}na chamada de log (ex.:logger.info("...", extra={"user_id": 42, "request_id": "abc"})), permitindo filtrar/agregar nos dashboards.extra={...}, captura de exceções comlogger.exception(...)).config/settings/base.py,config/settings/local.pyeconfig/settings/production.py(ambos os branches) montam o handleropensearcha partir de variáveis de ambiente. Os loggersrootedjangorecebem["console", "opensearch"]somente quando o handler está ativo; caso contrário, recebem apenas["console"](comportamento anterior preservado).{OPENSEARCH_LOGGING_INDEX_BASE}-{OPENSEARCH_LOGGING_ENVIRONMENT}(core-logs-prod,core-logs-hml,core-logs-dev) e o sufixo de data é aplicado em cima (ex.:core-logs-prod-2026.04.28). Mesmo com índices separados, o campoenvironmenté preservado dentro de todo documento (semântica no dado).OPENSEARCH_LOGGING_ENVIRONMENTdefaulta paraprodemproduction.pyedevembase.py/local.py, sempre overridável via env var.USE_OPENSEARCH_LOGGING,OPENSEARCH_LOGGING_HOSTS,OPENSEARCH_LOGGING_INDEX_BASE,OPENSEARCH_LOGGING_INDEX(escape hatch),OPENSEARCH_LOGGING_INDEX_DATE_FORMAT,OPENSEARCH_LOGGING_USER,OPENSEARCH_LOGGING_PASSWORD,OPENSEARCH_LOGGING_USE_SSL,OPENSEARCH_LOGGING_VERIFY_CERTS,OPENSEARCH_LOGGING_LEVEL,OPENSEARCH_LOGGING_ENVIRONMENT.opensearch-py==3.2.0emrequirements/base.txt(sem vulnerabilidades conhecidas no GitHub Advisory DB).Infraestrutura local para testar OpenSearch em desenvolvimento:
opensearch(porta9209→9200, single-node, sem TLS, security plugin desabilitado — apenas dev) eopensearch-dashboards(porta5609→5601) aolocal.yml, com volume persistenteopensearch_data..envs/.local/.djangodocumenta as novas variáveis de ambiente;USE_OPENSEARCH_LOGGING=noé o default para não alterar o comportamento atual. Basta trocar parayespara começar a enviar logs ao cluster local — o índice ficacore-logs-dev-YYYY.MM.DDe os dashboards em http://localhost:5609.config/settings/local.pyaplica defaults dev-friendly (hosthttp://opensearch:9200, sem SSL, sem verificação de certs, envdev) e usa a flagUSE_OPENSEARCH_LOGGINGpara alternar entre apenasconsoleeconsole + OpenSearchLogHandler.Bug latente corrigido: nomes com underscore (
_default_log_handlers,_opensearch_handler_enabled) não são exportados porfrom .base import *. O branch Sentry deproduction.pypassou a recomputar esses helpers localmente.Erros continuam sendo logados no console e enviados ao Sentry quando habilitado, agora também podem ser enviados ao OpenSearch — e nunca mais por email.
Onde a revisão poderia começar?
config/logging_handlers.py— implementação doOpenSearchLogHandler(incluindo docstring com exemplos e promoção deextra={...}para campos top-level).config/settings/base.py— bloco# OpenSearch logging(composição do índice, defaults) eLOGGING.config/settings/production.py— override deOPENSEARCH_LOGGING_ENVIRONMENT=prode blocoLOGGINGdentro deif env.bool("USE_SENTRY", ...).config/settings/local.py— bloco# OpenSearch logging (development).local.yml— serviçosopensearcheopensearch-dashboards..envs/.local/.django— variáveis novas.requirements/base.txt— dependênciaopensearch-py.Como este poderia ser testado manualmente?
Cenário 1 — Sem OpenSearch (default), sem emails:
DEBUG=False,ADMINSconfigurado,USE_OPENSEARCH_LOGGINGnão definido (ouFalse) eEMAIL_BACKENDapontando para um SMTP de teste (ex.:python -m smtpd -n -c DebuggingServer).logging.getLogger("django.request").error("teste")via shell).ADMINS, e o erro continua aparecendo no log do console.Cenário 2 — Com OpenSearch ativo em desenvolvimento (usando o
local.yml):.envs/.local/.django, definirUSE_OPENSEARCH_LOGGING=yes.docker compose -f local.yml up -d opensearch opensearch-dashboards django.logging.getLogger(__name__).info("teste", extra={"user_id": 42, "request_id": "abc-123"}).core-logs-dev-YYYY.MM.DD— confirmar que o documento contém@timestamp,level,logger,message,module,host,service,environment="dev", e os campos extrasuser_id=42,request_id="abc-123"como top-level.Cenário 3 — Com OpenSearch ativo em produção:
USE_OPENSEARCH_LOGGING=True,OPENSEARCH_LOGGING_HOSTS=https://os.example.org:9200, opcionalmenteOPENSEARCH_LOGGING_USER/PASSWORDe demais variáveis.OPENSEARCH_LOGGING_ENVIRONMENTdefaulta paraprod.core-logs-prod-YYYY.MM.DD.Verificação programática equivalente do bloqueio de email:
Algum cenário de contexto que queira dar?
O
LOGGINGdo projeto não definiamail_adminsnem o loggerdjango, dando a impressão de que nenhum email seria enviado. Porém,django.utils.log.configure_loggingaplicaDEFAULT_LOGGINGviadictConfigantes doLOGGINGdo usuário; comdisable_existing_loggers: False(o caso debase.py), oAdminEmailHandlerpreviamente anexado ao loggerdjangosobrevive à reconfiguração — daí a necessidade de redefinir explicitamente o loggerdjango, e não apenas o handlermail_admins.A adição do sink de OpenSearch atende à orientação do revisor de tratar a forma de registro de logs: em vez de apenas silenciar o canal de email, a aplicação passa a ter um destino centralizado e estruturado para logs (OpenSearch) quando configurado, mantendo o console como fallback seguro. Os refinamentos posteriores (índices separados por ambiente mantendo o campo
environmentno dado, defaultsprod/dev, infra local comopensearch+opensearch-dashboardse flag para alternar entre console e OpenSearch, além de exemplos de uso comextra={...}para campos contextuais) atendem aos comentários de revisão e tornam o sink utilizável de ponta a ponta tanto em produção quanto em desenvolvimento.Screenshots
N/A — alterações apenas em configuração de logging, em um novo módulo de handler e na infraestrutura docker-compose de desenvolvimento.
Referências
AdminEmailHandler: https://docs.djangoproject.com/en/stable/topics/logging/#django.utils.log.AdminEmailHandlerlogging.config.dictConfigedisable_existing_loggers: https://docs.python.org/3/library/logging.config.html#dictionary-schema-detailsopensearch-py: https://github.com/opensearch-project/opensearch-py