1/** 2 * OpenTelemetry Browser â AngularJS 1.x 3 * Self-contained: no depende de bundles CDN externos. 4 * Intercepta XHR ($http de AngularJS), inyecta W3C traceparent, 5 * y envÃa spans OTLP-JSON a /otel/v1/traces (nginx proxy â collector). 6 * 7 * service.name / deployment.environment se DERIVAN del hostname 8 * (servicehub live/stg, trucare, serveco) â no van hardcodeados. 9 * Además captura errores JS no controlados (window.onerror, 10 * unhandledrejection) y expone shOtelReportError() para el front. 11 */ 12(function (global) { 13 'use strict'; 14 15 var hostname = global.location ? global.location.hostname : ''; 16 17 // ââ Resolver servicio/entorno desde el hostname âââââââââââââââââââ 18 // Misma clasificación de marca/ambiente que usa app.js. Devuelve null 19 // para entornos sin collector (dev directo en EC2) â OTel desactivado. 20 // Sólo se cambian las ETIQUETAS; el destino (/otel/v1/traces) lo decide nginx. 21 function resolveTarget(h) { 22 if (!h || h.indexOf('development.') !== -1) return null; // dev directo: sin collector 23 if (h.indexOf('trucareprotection') !== -1) return { service: 'live-trucareprotection-web', env: 'live' }; 24 if (h.indexOf('servecosoftware') !== -1) return { service: 'live-serveco-web', env: 'live' }; 25 if (h.indexOf('staging.') !== -1 || h.indexOf('stg.') !== -1 || 26 h.indexOf('replica') !== -1 || h.indexOf('it.') !== -1 || 27 h.indexOf('dev.') !== -1) return { service: 'stg-servicehub-web', env: 'stg' }; 28 if (h.indexOf('servicehubcrm.net') !== -1 || 29 h.indexOf('servicehub.live') !== -1) return { service: 'live-servicehub-web', env: 'live' }; 30 return { service: 'servicehub-web', env: 'unknown' }; 31 } 32 33 var _target = resolveTarget(hostname); 34 if (!_target) { 35 console.info('[OTel] Desactivado en entorno de desarrollo (' + hostname + ')'); 36 global.shOtelSetUser = function () {}; 37 global.shOtelReportError = function () {}; 38 return; 39 } 40 41 var _resourceAttrs = [ 42 { key: 'service.name', value: { stringValue: _target.service } }, 43 { key: 'deployment.environment', value: { stringValue: _target.env } }, 44 { key: 'app.framework', value: { stringValue: 'angularjs-1.x' } } 45 ]; 46 47 // ââ Utilidades ââââââââââââââââââââââââââââââââââââââââââââââââââââ 48 function randHex(len) { 49 var result = ''; 50 var chars = '0123456789abcdef'; 51 for (var i = 0; i < len; i++) { 52 result += chars[Math.floor(Math.random() * 16)]; 53 } 54 return result; 55 } 56 57 function nowNs() { 58 var ms = (global.performance && global.performance.now) 59 ? global.performance.timeOrigin + global.performance.now() 60 : Date.now(); 61 return Math.floor(ms * 1000000); 62 } 63 64 // ââ Estado de usuario âââââââââââââââââââââââââââââââââââââââââââââ 65 var _user = {}; 66 67 // ââ Atributos base (host + usuario) âââââââââââââââââââââââââââââââ 68 function baseAttrs() { 69 var attrs = [{ key: 'net.peer.name', value: { stringValue: hostname } }]; 70 if (_user.login_id) attrs.push({ key: 'servicehub.user.login_id', value: { intValue: _user.login_
70id } }); 71 if (_user.user_id) attrs.push({ key: 'enduser.id', value: { intValue: _user.user_id } }); 72 if (_user.user_id) attrs.push({ key: 'servicehub.user.id', value: { intValue: _user.user_id } }); 73 if (_user.user_name) attrs.push({ key: 'servicehub.user.name', value: { stringValue: _user.user_name } }); 74 if (_user.company_id) attrs.push({ key: 'servicehub.company.id', value: { intValue: _user.company_id } }); 75 return attrs; 76 } 77 78 // ââ POST OTLP-JSON (usa XHR nativo â NO el proxy) âââââââââââââââââ 79 function postOtlp(payload) { 80 try { 81 var native = new global._OriginalXMLHttpRequest(); 82 native.open('POST', '/otel/v1/traces', true); 83 native.setRequestHeader('Content-Type', 'application/json'); 84 native.send(JSON.stringify(payload)); 85 } catch (e) { /* silencioso */ } 86 } 87 88 // ââ EnvÃo de span de petición XHR âââââââââââââââââââââââââââââââââ 89 function sendSpan(span) { 90 try { 91 var attrs = baseAttrs(); 92 attrs.push({ key: 'http.method', value: { stringValue: span.method } }); 93 attrs.push({ key: 'http.url', value: { stringValue: span.url } }); 94 attrs.push({ key: 'http.status_code', value: { intValue: span.status } }); 95 96 postOtlp({ 97 resourceSpans: [{ 98 resource: { attributes: _resourceAttrs }, 99 scopeSpans: [{ 100 scope: { name: 'sh-otel-init', version: '1.1.0' }, 101 spans: [{ 102 traceId: span.traceId, 103 spanId: span.spanId, 104 parentSpanId: '', 105 name: span.method + ' ' + span.path, 106 kind: 3, 107 startTimeUnixNano: String(span.startNs), 108 endTimeUnixNano: String(span.endNs), 109 attributes: attrs, 110 status: { code: span.status >= 400 ? 2 : 1 } 111 }] 112 }] 113 }] 114 }); 115 } catch (e) { /* silencioso */ } 116 } 117 118 // ââ EnvÃo de span de error JS (no controlado) âââââââââââââââââââââ 119 function sendErrorSpan(source, message, stack, info) { 120 try { 121 info = info || {}; 122 var t = nowNs(); 123 var attrs = baseAttrs(); 124 attrs.push({ key: 'error.source', value: { stringValue: String(source || 'unknown') } }); 125 attrs.push({ key: 'exception.type', value: { stringValue: String(info.type || 'Error') } }); 126 attrs.push({ key: 'exception.message', value: { stringValue: String(message || '').slice(0, 2000) } }); 127 if (stack) attrs.push({ key: 'exception.stacktrace', value: { stringValue: String(stack).slice(0, 8000) } }); 128 if (info.loc) attrs.push({ key: 'code.filepath', value: { stringValue: String(info.loc) } }); 129 130 postOtlp({ 131 resourceSpans: [{ 132 resource: { attributes: _resourceAttrs }, 133 scopeSpans: [{ 134 scope: { name: 'sh-otel-init', version: '1.1.0' }, 135 spans: [{ 136 traceId: randHex(32), 137 spanId: randHex(16), 138 parentSpanId: '', 139 name: 'js.error', 140 kind: 1, 141 startTimeUnixNano: String(t), 142 endTimeUnixNano: String(t), 143 attributes: attrs, 144 status: { code: 2, message: String(message || '').slice(0, 200) }, 145 events: [{ 146 timeUnixNano: String(t), 147 name: 'exception', 148 attributes: [ 149 { key: 'exception.type', value: { stringValue: String(info.type || 'Error') } }, 150 { key: 'exception.message', value: { stringValue: String(message || '') } } 151 ].concat(stack ? [{ key: 'exception.stacktrace', value: { stringValue: String(stack) } }] : []) 152 }] 153 }] 154 }] 155 }] 156 }); 157 } catch (e) { /* silencioso */ } 158 } 159 160 // ââ Guardar XHR original ââââââââââââââââââââââââââââââââââââââââââ 161 global._OriginalXMLHttpRequest = global.XMLHttpRequest; 162 163 // ââ Proxy XHR ââââââââââââââââââââââââââââââââââââââââââââââââââââ 164 global.XMLHttpRequest = function () { 165 var _native = new global._OriginalXMLHttpRequest(); 166 var _method = ''; 167 var _url = ''; 168 var _path = ''; 169 var _skip = false;
170 var _traceId, _spanId, _startNs; 171 172 var self = this; 173 174 // Delegar propiedades al nativo 175 var props = [ 176 'readyState','response','responseText','responseType','responseURL', 177 'responseXML','status','statusText','timeout','upload','withCredentials' 178 ]; 179 props.forEach(function (p) { 180 Object.defineProperty(self, p, { 181 get: function () { return _native[p]; }, 182 set: function (v) { _native[p] = v; } 183 }); 184 }); 185 186 // Delegar event handlers al nativo 187 var handlers = ['onreadystatechange','onload','onerror','ontimeout','onprogress','onabort','onloadend','onloadstart']; 188 handlers.forEach(function (h) { 189 Object.defineProperty(self, h, { 190 get: function () { return _native[h]; }, 191 set: function (v) { _native[h] = v; } 192 }); 193 }); 194 195 self.open = function (method, url) { 196 _method = (method || 'GET').toUpperCase(); 197 _url = url || ''; 198 _path = _url.replace(/\?.*/, '').replace(/^https?:\/\/[^/]+/, '') || '/'; 199 _skip = _url.indexOf('/otel/') !== -1; 200 201 if (!_skip) { 202 _traceId = randHex(32); 203 _spanId = randHex(16); 204 _startNs = nowNs(); 205 } 206 return _native.open.apply(_native, arguments); 207 }; 208 209 self.setRequestHeader = function (name, value) { 210 return _native.setRequestHeader(name, value); 211 }; 212 213 self.send = function (body) { 214 if (!_skip && _traceId) { 215 try { 216 _native.setRequestHeader('traceparent', '00-' + _traceId + '-' + _spanId + '-01'); 217 } catch (e) { /* ignorar */ } 218 219 // Usar addEventListener para NO sobreescribir onreadystatechange del caller 220 _native.addEventListener('loadend', function () { 221 sendSpan({ 222 traceId: _traceId, 223 spanId: _spanId, 224 startNs: _startNs, 225 endNs: nowNs(), 226 method: _method, 227 url: _url, 228 path: _path, 229 status: _native.status || 0 230 }); 231 }); 232 } 233 return _native.send(body); 234 }; 235 236 self.abort = function () { return _native.abort(); }; 237 self.getAllResponseHeaders = function () { return _native.getAllResponseHeaders(); }; 238 self.getResponseHeader = function (h) { return _native.getResponseHeader(h); }; 239 self.overrideMimeType = function (m) { return _native.overrideMimeType(m); }; 240 self.addEventListener = function () { return _native.addEventListener.apply(_native, arguments); }; 241 self.removeEventListener = function () { return _native.removeEventListener.apply(_native, arguments); }; 242 }; 243 244 // ââ API pública para setear usuario desde AngularJS âââââââââââââââ 245 global.shOtelSetUser = function (user) { 246 if (!user) return;
247 if (user.login_user_id) _user.login_id = parseInt(user.login_user_id, 10); 248 if (user.user_id) _user.user_id = parseInt(user.user_id, 10); 249 if (user.company_id) _user.company_id = parseInt(user.company_id, 10); 250 if (user.fname || user.lname) { 251 _user.user_name = ((user.fname || '') + ' ' + (user.lname || '')).trim(); 252 } 253 }; 254 255 // ââ Captura de errores JS no controlados ââââââââââââââââââââââââââ 256 global.addEventListener('error', function (e) { 257 try { 258 if (!e) return; 259 // Ignorar errores de carga de recursos (img/script/css 404) â no son errores JS 260 if (e.target && e.target !== global && e.target.nodeName) return; 261 var err = e.error || {}; 262 var loc = e.filename ? (e.filename + ':' + (e.lineno || 0) + ':' + (e.colno || 0)) : undefined; 263 sendErrorSpan('window.onerror', e.message || err.message, err.stack, { type: err.name, loc: loc }); 264 } catch (ex) { /* silencioso */ } 265 }); 266 267 global.addEventListener('unhandledrejection', function (e) { 268 try { 269 var r = e ? e.reason : null; 270 var msg = (r && r.message) ? r.message : String(r); 271 sendErrorSpan('unhandledrejection', msg, r && r.stack, { type: r && r.name }); 272 } catch (ex) { /* silencioso */ } 273 }); 274 275 // Reporte manual de errores (p.ej. desde $exceptionHandler de AngularJS) 276 global.shOtelReportError = function (err, source) { 277 if (!err) return; 278 var msg = (err && err.message) ? err.message : String(err); 279 sendErrorSpan(source || 'manual', msg, err && err.stack, { type: err && err.name }); 280 }; 281 282 console.info('[OTel] Browser tracing activo (' + _target.service + ') â /otel/v1/traces'); 283 284}(window));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.