Skip to content

Logfire browser not working with electron #1357

@bruno-borges-2001

Description

@bruno-borges-2001

Description

I'm working on an Electron based project and sending the logs with logfire to a proxy via @pydantic/logfire-browser configured like this:

  logfire.configure({
    traceUrl,
    environment: isDev ? 'development' : 'production',
    serviceName: app.getName(),
    serviceVersion: app.getVersion(),
    instrumentations: [getWebAutoInstrumentations()], // <- from @opentelemetry/auto-instrumentations-web
  });

I noticed that at first, logs from development mode (when the app was not packaged yet) did not show in the platform. When diving into the error, I found that it was because XMLHttpRequest was being used in the @opentelemetry/otlp-exporter-base but it was not defined (probably because of the electron environment).

This error started happening on production (packaged app) as well, and making a patch that replaced the XMLHttpRequest with regular fetch call was enough to make it work again. I wanted to know if this is a bug or it is intentional and there is something different that is needed to be done on electron projects

Patch:

diff --git a/node_modules/@opentelemetry/otlp-exporter-base/build/src/transport/xhr-transport.js b/node_modules/@opentelemetry/otlp-exporter-base/build/src/transport/xhr-transport.js
index 47dc7bf..eefa3ad 100644
--- a/node_modules/@opentelemetry/otlp-exporter-base/build/src/transport/xhr-transport.js
+++ b/node_modules/@opentelemetry/otlp-exporter-base/build/src/transport/xhr-transport.js
@@ -23,62 +23,58 @@ class XhrTransport {
     constructor(_parameters) {
         this._parameters = _parameters;
     }
-    send(data, timeoutMillis) {
-        return new Promise(resolve => {
-            const xhr = new XMLHttpRequest();
-            xhr.timeout = timeoutMillis;
-            xhr.open('POST', this._parameters.url);
+    async send(data, timeoutMillis) {
+        try {
+            const controller = new AbortController();
+            const timeoutId = setTimeout(() => controller.abort(), timeoutMillis);
+            
             const headers = this._parameters.headers();
-            Object.entries(headers).forEach(([k, v]) => {
-                xhr.setRequestHeader(k, v);
+            const response = await fetch(this._parameters.url, {
+                method: 'POST',
+                headers: headers,
+                body: data,
+                signal: controller.signal
             });
-            xhr.ontimeout = _ => {
-                resolve({
+            
+            clearTimeout(timeoutId);
+            
+            if (response.status >= 200 && response.status <= 299) {
+                api_1.diag.debug('Fetch request success');
+                return {
+                    status: 'success',
+                };
+            } else if ((0, is_export_retryable_1.isExportRetryable)(response.status)) {
+                const retryAfter = response.headers.get('Retry-After');
+                return {
+                    status: 'retryable',
+                    retryInMillis: (0, is_export_retryable_1.parseRetryAfterToMills)(retryAfter),
+                };
+            } else {
+                return {
                     status: 'failure',
-                    error: new Error('XHR request timed out'),
-                });
-            };
-            xhr.onreadystatechange = () => {
-                if (xhr.status >= 200 && xhr.status <= 299) {
-                    api_1.diag.debug('XHR success');
-                    resolve({
-                        status: 'success',
-                    });
-                }
-                else if (xhr.status && (0, is_export_retryable_1.isExportRetryable)(xhr.status)) {
-                    resolve({
-                        status: 'retryable',
-                        retryInMillis: (0, is_export_retryable_1.parseRetryAfterToMills)(xhr.getResponseHeader('Retry-After')),
-                    });
-                }
-                else if (xhr.status !== 0) {
-                    resolve({
-                        status: 'failure',
-                        error: new Error('XHR request failed with non-retryable status'),
-                    });
-                }
-            };
-            xhr.onabort = () => {
-                resolve({
+                    error: new Error('Fetch request failed with non-retryable status'),
+                };
+            }
+        } catch (error) {
+            if (error.name === 'AbortError') {
+                return {
                     status: 'failure',
-                    error: new Error('XHR request aborted'),
-                });
-            };
-            xhr.onerror = () => {
-                resolve({
+                    error: new Error('Fetch request timed out'),
+                };
+            } else {
+                return {
                     status: 'failure',
-                    error: new Error('XHR request errored'),
-                });
-            };
-            xhr.send(data);
-        });
+                    error: new Error(`Fetch request failed: ${error.message}`),
+                };
+            }
+        }
     }
     shutdown() {
         // Intentionally left empty, nothing to do.
     }
 }
 /**
- * Creates an exporter transport that uses XHR to send the data
+ * Creates an exporter transport that uses fetch to send the data
  * @param parameters applied to each request made by transport
  */
 function createXhrTransport(parameters) {

Python, Logfire & OS Versions, related packages (not required)

"@opentelemetry/auto-instrumentations-web": "^0.49.0",
"@pydantic/logfire-browser": "^0.9.0",
"electron": "^37.2.6"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions