Sentry-javascript: Carregamento assíncrono e erros de captura

Criado em 18 dez. 2013  ·  36Comentários  ·  Fonte: getsentry/sentry-javascript

Quero ser capaz de carregar raven.js de forma assíncrona, mas ainda ser capaz de capturar erros enquanto o script está sendo carregado. (Algo parecido com a forma como o Google Analytics lida com eventos, armazenando-os em uma variável até que a biblioteca carregue).

Aqui está o que tenho até agora: https://gist.github.com/karolisdzeja/8010574

Mas fazer dessa forma perde algumas das informações e detalhes que Raven normalmente fornece. Existe uma maneira de armazenar as informações de erro completas de forma semelhante a esta?

Feature

Comentários muito úteis

Aqui está o snippet que usamos para enfileirar chamadas para métodos Raven de forma transparente: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

O mock certamente poderia ser melhorado, mas não precisamos replicar mais funcionalidades. Object.defineProperty nos permite conectar logo após Raven anexar-se à janela, mas talvez o evento de carregamento do script seja o suficiente. Seria bom ter uma maneira oficial de habilitar isso.

Todos 36 comentários

+1

+1

+1

+1

Todos que comentaram sobre isso - o que há de errado com a solução de @karolisdzeja?

Em última análise, não tenho certeza de como podemos adicionar um recurso ao código-fonte Raven.js que deve funcionar quando o código-fonte Raven.js não está na página. Acho que, em última análise, sempre será uma solução personalizada; na melhor das hipóteses, poderíamos adicionar um "como fazer" aos nossos documentos.

@benvinegar a solução parece boa, mas seria melhor se fosse oficialmente suportada e documentada. Estou mais feliz apenas por ter que confiar na equipe do Sentinela ao invés de avaliar uma essência aleatória.

Na verdade, uma solução muito melhor seria algo como o código JS SDK do Twitter: https://dev.twitter.com/web/javascript/loading

Configure uma fila de funções no carregamento da página que será consumida quando o js externo for carregado, substituindo o objeto proxy. E certifique-se de que todas as chamadas de API passam por algo como uma chamada .ready () para o proxy.

Isso garante que qualquer chamada possa ser enfileirada antes que o js seja carregado, em vez de apenas captureMessage, sem ter que fazer proxy de cada função individualmente.

Eu adoraria poder carregar raven.js de forma assíncrona / adiada e não ter que me preocupar.

Outros problemas com a essência: ele destrói window.onerror e introduz algumas variáveis ​​globais não contidas.

Ele também não usa a função traceKitWindowOnError completa que o raven.js usa quando carrega.

Refiz ligeiramente a essência acima: https://gist.github.com/oroce/ec3786ba7eff59963842220c3ffc56b4

Não há nenhuma variável de vazamento. Eu mantive o manipulador window.onerror , mas fique à vontade para usar window.addEventListener('error', fn) .

A maior ajuda no momento é ter traceKitWindowOnError como uma função exportada do Raven. Uma vez que essa é a função que é chamada quando ocorre um erro: https://github.com/getsentry/raven-js/blob/master/dist/raven.js#L2074

Eu sei que não teríamos esse rastreamento de pilha muito adequado, mas teríamos algo melhor do que o que temos agora.

Existem outras desvantagens em fazer isso:

  • Contando com window.onerror para detectar erros antes que o Raven seja carregado, os rastreamentos de pilha não estão disponíveis para todos os navegadores

    • além de não ter um rastreamento de pilha, isso afetará o agrupamento negativamente

    • é por isso que install() tenta / captura a instrumentação

  • Traços sintéticos não funcionarão (todos eles aparecerão como originários deste código)
  • Sem coleção de migalhas de pão

Portanto, você está trocando um desempenho potencialmente melhor por relatórios de erros de qualidade inferior. Se a execução assíncrona for importante, eu recomendo antes que você agrupe o Raven com seu próprio código para que seja servido junto.

@benvinegar você está completamente certo. Em aplicativos que não são públicos (também conhecido como google não alcançará as páginas), o método clássico (bloqueio) do raven é completamente aceitável, mas assim que você tiver um site voltado para o público onde os pontos de insight da página do google importam, precisamos otimizar como carregamos o código de terceiros (este é o preço que estamos dispostos a pagar em favor de ux, velocidade e melhor posição nos resultados de pesquisa).

Além disso, agrupar o raven em nosso pacote é uma solução, mas assim que você começar a otimizar seu código de front-end com otimizações acima da dobra, como dividir seu pacote em vários usando ferramentas como pacote de fator ou incluir vários pacotes para ganhar mais velocidade, a solução acima pode ser melhor, mas estou aberto a sugestões.

Todos eles têm vantagens e desvantagens, então seria ótimo se pudéssemos documentar todas as estratégias disponíveis, portanto, dependendo de cada aplicativo da web específico, aplicaremos estratégias diferentes.
Com a estratégia assíncrona, devemos carregar o script após o evento onload , em vez de carregar apenas async, para evitar o bloqueio do evento onload , renderização.

/**
 * Setup Js error lazy tracking
 * - Pros: doesn't block rendering, onload event
 * - Cons: lower quality error reports for lazy errors
 *
 * <strong i="9">@author</strong> vinhlh
 *
 * <strong i="10">@param</strong>  {object} window
 * <strong i="11">@param</strong>  {object} labJs
 * <strong i="12">@param</strong>  {string} ravenCdn
 * <strong i="13">@param</strong>  {string} sentryDsn
 */
(function(window, labJs, ravenCdn, sentryDsn) {
  var errors = [];
  var oldOnError = window.onerror;

  window.onerror = function() {
    errors.push(arguments);
    oldOnError && oldOnError.apply(this, arguments);
  };
  window.addEventListener('load', function() {
    labJs
      .script(ravenCdn)
      .wait(function() {
        window.onerror = oldOnError;
        Raven.config(sentryDsn).install();
        errors.forEach(function(args) {
          window.onerror.apply(this, args);
        });
      });
  });
})(window, $LAB, 'raven-js-3.8.1/dist/raven.js', 'https://[email protected]/9');

Acho que provavelmente apenas documentaremos um snippet assíncrono, como os fornecidos acima, mas mencionamos que ele vem com vantagens e desvantagens.

Só mais um comentário. Essas compensações podem parecer aceitáveis, mas eu lido com muitos tíquetes de suporte de usuários sobre erros de baixa fidelidade que eles estão enfrentando e que (incorretamente) se acredita serem derivados de Raven.js. Meu medo é que, se eu encorajar as pessoas a usarem a abordagem assíncrona, mais e mais pessoas me perguntem "por que não há rastreamento de pilha" e outras reclamações quando é porque essa abordagem é de baixa fidelidade. Estou disposto a aceitar isso, mas é uma pílula difícil de engolir. 😓

@benvinegar Eu

@oroce - sim, isso não é 100% uma preocupação com as pessoas neste tópico, mas com pessoas que podem seguir essa estratégia sem entender as advertências corretamente (por exemplo, apenas copiar / colar).

Vou manter esse problema aberto, com um plano para adicionar o snippet aos documentos Install - e vou colocar um monte de avisos em todos os lugares.

Obrigado novamente por sua participação aqui / me convencer a fazer isso.

Aqui está o snippet que usamos para enfileirar chamadas para métodos Raven de forma transparente: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

O mock certamente poderia ser melhorado, mas não precisamos replicar mais funcionalidades. Object.defineProperty nos permite conectar logo após Raven anexar-se à janela, mas talvez o evento de carregamento do script seja o suficiente. Seria bom ter uma maneira oficial de habilitar isso.

Ei, pessoal, gostaria de saber se há algo de errado com a maneira como o Raygun faz isso de forma assíncrona?
Não tenho certeza, mas parece lidar bem com casos extremos? Eu posso estar errado embora :)

@ Kl0tl muito bom obrigado

Isso é muito simples usando uma importação dinâmica . Ainda em stage3, mas suportado por webpack.

Simplesmente usamos a promessa como uma fila. Depois de preenchidos, todos os retornos de chamada são executados em ordem.

const RavenPromise = import('raven-js'); // async load raven bundle

// initial setup
RavenPromise.then(Raven => {
    Raven.config('url-to-sentry', options).install();
}):

// exported log function
export const logMessage = (level, logger, text) => {
    RavenPromise.then(Raven => {
        Raven.captureMessage(text, {level, logger});
    });
};

Da mesma forma que @zanona, eu também preferiria ter um código de rastreamento simples como o Raygun ou o Google Analytics . Aqui está um exemplo para analytics.js :

<script async src="https://www.google-analytics.com/analytics.js"></script>
<script>
    window.ga = window.ga || function () {
        (ga.q = ga.q || []).push(arguments)
    }
    ga.l = +new Date

    ga('create', 'UA-XXXXX-Y', 'auto')
    ga('send', 'pageview')
</script>

@benvinegar Algo assim é possível com Raven.js? Talvez no futuro?

@kireerik , com certeza será implementado (provavelmente como uma documentação de como fazer), mas não posso dar uma estimativa de data precisa agora.

@kamilogorek Parece ótimo (não gosto da solução alternativa). Sem problemas!

Para qualquer pessoa interessada, apresentei uma ideia de outra maneira de carregar ravenjs de forma assíncrona:
https://gist.github.com/MaxMilton/e2338b02b7381fc7bef2ccd96f434201

É baseado no código de @oroce, mas com as principais diferenças sendo que eu uso uma tag <script async src'='..."> regular no cabeçalho do documento para melhor desempenho (os navegadores podem agendar a busca do recurso mais cedo) + Eu não me incomodo em agrupá-la um IIFE e outros pequenos ajustes.

@MaxMilton Legal! Eu criei meu próprio sabor baseado no seu:

<script async src="https://cdn.ravenjs.com/3.22.1/raven.min.js" crossorigin="anonymous" id="raven"></script>
<script>
    (function (sentryDataSourceName) {
        var raven = document.getElementById('raven')
        , isNotLoaded = true
        , errors = []
        raven.onreadystatechange = raven.onload = function () {
            if (isNotLoaded) {
                Raven.config(sentryDataSourceName).install()
                isNotLoaded = !isNotLoaded
                errors.forEach(function (error) {
                    Raven.captureException(error[4] || new Error(error[0]), {
                        extra: {
                            file: error[1]
                            , line: error[2]
                            , col: error[3]
                        }
                    })
                })
            }
        }
        window.onerror = function (message, source, lineNumber, colmnNumber, error) {
            if (isNotLoaded)
                errors.push([message, source, lineNumber, colmnNumber, error])
        }
    })('https://<key>@sentry.io/<project>')
</script>

Eu também tenho algumas perguntas:

  • É necessário definir o crossorigin atributo no script tag?
  • É suficiente passar apenas o objeto de erro em vez da outra solução ?

O que você acha? Qual é a opinião do autor (@kamilogorek) sobre isso?

@kireerik quando você coloca crossorigin="anonymous" em scripts, permite que você capture totalmente os erros (daquele script externo) com o evento window.onerror . Também evita que o navegador envie credenciais com a solicitação de busca, que normalmente é o que você deseja com recursos de terceiros. Referência MDN 1 , referência MDN 2 .

Você pode simplesmente passar o erro e ele funcionará _ na maior parte_ do tempo. A ressalva de que navegadores antigos (por exemplo, Firefox antes da versão 31) não passam as propriedades columnNo ou Error Object para o evento window.onerror . Portanto, se você deseja uma compatibilidade realmente boa, precisa fazer um pouco mais. Referência MDN .

EDIT: Dica bônus: acontece que quando você coloca crossorigin sem nenhum valor, é tratado da mesma forma que crossorigin="anonymous" .

Para sua informação, atualizei minha essência anterior para algo que está muito mais pronto para a produção:

  • muitos comentários para explicar o que realmente está acontecendo
  • grande limpeza + usar nomes de variáveis ​​descritivas (sempre um bom bônus: wink:)
  • envolva em IIFE para não poluir o namespace global
  • consertar parâmetros incorretos passados ​​para o item da matriz de erro

Veja a essência se quiser entender tudo OU se preferir copiar e colar rápido, aqui está a versão reduzida:

<!-- Sentry JS error tracking -->
<script async src="https://cdn.ravenjs.com/3.22.0/raven.min.js" crossorigin id="raven"></script>
<script>
(function(b,e,c,d){b.onerror=function(a,b,d,f,g){c||e.push([a,b,d,f,g])};b.onunhandledrejection=function(a){c||e.push([a.reason.reason||a.reason.message,a.type,JSON.stringify(a.reason)])};d.onreadystatechange=d.onload=function(){c||(c=!0,
Raven.config("___PUBLIC_DSN___").install(),
b.onunhandledrejection=function(a){Raven.captureException(Error(a.reason.reason||a.reason.message),{extra:{type:a.type,reason:JSON.stringify(a.reason)}})},e.forEach(function(a){Raven.captureException(a[4]||Error(a[0]),{extra:{file:a[1],line:a[2],col:a[3]}})}))}})(window,[],!1,document.getElementById("raven"));
</script>

<link rel="preconnect" href="https://sentry.io">

Substitua ___PUBLIC_DSN___ pelo seu DSN e cole-o em algum lugar na cabeça perto da tag de fechamento </head> . Ou se você é um hipster que não usa mais as tags <head> e <body> então apenas cole próximo ao topo após quaisquer recursos críticos / de aplicativos (por exemplo, CSS). Idealmente, deve ser antes de qualquer outro JavaScript, para que você possa capturar erros de scripts carregados após esse código.

Em meus testes rápidos, não houve nenhum problema, então não vejo razão para não usar isso em vez da versão síncrona padrão.

Se alguém tiver uma ideia para uma abordagem melhor, estou ansioso para ouvi-la.

Edit: Desculpe por editar este comentário tantas vezes. Está em um nível estável agora. Foi divertido chegar a este estado! :risonho:

Depois que a biblioteca do Sentry é carregada, a qualidade do relatório de erros é exatamente a mesma que a sincronização de carregamento? (Presumo que sim, apenas verificando)

Também nos documentos que você pode querer adicionar, você não pode usar o Raven até que a lib seja carregada, talvez forneça uma função de retorno de chamada nas opções para que possa definir o contexto do usuário, etc?

concordo com @ dalyr95 . uma função de retorno de chamada seria útil. talvez um evento de carregamento raven personalizado.

Tenho um pedido semelhante a @ dalyr95. No momento, a única maneira de chamar setUserContext() é modificar o fragmento do carregador, que não é tão limpo quanto ser capaz de passar um retorno de chamada no objeto de configuração principal.

Olá, obrigado por relatar o problema.

Estamos trabalhando na próxima versão principal de nosso SDK.
Por causa disso, tivemos que colocar o trabalho na versão atual (exceto bugs principais ou de segurança) em espera.
Tentaremos retornar a todos os relatórios o mais rápido possível, portanto, seja paciente.

Obrigado pela sua compreensão,
Felicidades!

Minha solução foi adicionar 'undefined'!=k.setup&&k.setup(); imediatamente após a chamada para .install() , então adicionei uma função chamada setup a SENTRY_SDK com meu código de inicialização post.

Com o carregador assíncrono, fui capaz de definir o contexto do usuário e outras informações, passando-o como um segundo argumento para Raven.config :

<script>
    Raven.config("https://<mydsn>@sentry.io/<projectid>", 
      {"release":"0.3.1",
       "environment":"dev",
       "user": {"id":"7b031fa0-32ff-46fe-b94b-e6bc201c3c5f",
                "organisation-id":"b1a50c41-b85e-4c50-9cec-c55ff36cf6d1"}}).install();
</script>

Acho que já existe tudo para isso, só poderia ser melhor documentado.

@danielcompton só pode obter informações do usuário por meio da API de back-end?

Esta página foi útil?
0 / 5 - 0 avaliações