I have a server side rendered web application using express that we were sending tracing info to Google using #google-cloud/trace-agent which worked fine. But THEN.... we bundled our application, and all trace information disappeared. We still see the requests in google cloud console, but now there are no child spans.
I scoured the documentation and stumbled upon this disappointing passage:
Tracing bundled or webpacked server code.
unsupported
The Trace Agent does not support bundled server code, so bundlers like webpack or #zeit/ncc will not work.
From: https://github.com/googleapis/cloud-trace-nodejs#tracing-bundled-or-webpacked-server-code
which I thought just meant that I need to start my own root spans... but that doesn't seem to be part of the API.
Does this mean I can't send spans in a bundled server? Is there a programmatic way to manually start root spans in express?
I ended up copying the middleware function from https://github.com/googleapis/cloud-trace-nodejs/blob/main/src/plugins/plugin-express.ts and adding it to my express app manually via app.use(...).
For completeness, the code is:
import { get as getTracer } from '#google-cloud/trace-agent';
import type { RootSpanOptions } from '#google-cloud/trace-agent/build/src/plugin-types';
import type { NextFunction, Request, Response } from 'express';
export function googleCloudTracing(request: Request, response: Response, next: NextFunction): void {
const tracer = getTracer();
const options: RootSpanOptions = {
name: request.path,
traceContext: tracer.propagation.extract(key => request.get(key)),
url: request.originalUrl,
method: request.method,
skipFrames: 1,
};
tracer.runInRootSpan(options, rootSpan => {
// Set response trace context.
const responseTraceContext = tracer.getResponseTraceContext(
options.traceContext!,
tracer.isRealSpan(rootSpan)
);
if (responseTraceContext) {
// This propagates the likes of tracer.constants.TRACE_CONTEXT_HEADER_NAME for cross-service tracing.
tracer.propagation.inject((k, v) => response.setHeader(k, v), responseTraceContext);
}
if (!tracer.isRealSpan(rootSpan)) {
next();
return;
}
tracer.wrapEmitter(request);
tracer.wrapEmitter(response);
const url = `${request.protocol}://${request.headers.host}${request.originalUrl}`;
rootSpan.addLabel(tracer.labels.HTTP_METHOD_LABEL_KEY, request.method);
rootSpan.addLabel(tracer.labels.HTTP_URL_LABEL_KEY, url);
rootSpan.addLabel(tracer.labels.HTTP_SOURCE_IP, request.ip);
response.on('finish', () => {
request.route?.path && rootSpan.addLabel('express/request.route.path', request.route.path);
rootSpan.addLabel(tracer.labels.HTTP_RESPONSE_CODE_LABEL_KEY, response.statusCode);
rootSpan.endSpan();
logger.info('Request tracing ended.');
});
next();
});
}
Related
I'm implementing Headless Wordpress using Faust.js and ran into a problem. The current Wordpress backend requires basic http authentication, protected by base64-encoded credentials, before being able to access the backend site contents and I'm running the frontend with Faust.js. On a local environment, there is no need to implement adding the credentials to the header but on production (because the http authentication exists), I can't retrieve the post content as well as other assets such as images, etc. from the Wordpress backend.
I was doing some research into how to add the http authentication but so far I've only found limited examples of how to implement basic authentication to do this. One is with typescript (https://github.com/wpengine/faustjs/issues/845) but since I currently have the project on js code, it seems I would need to convert a lot of the files into typescript (maybe including the packages included in node_modules which I don't want to break if I did the conversion). I want to find a way to add this http basic authentication as part of the request header on my Faust.js frontend project without converting to js.
On the example, I've tried implementing this with the ts example, while using js code, but I'm getting all sorts of errors, when building it. Here's the code:
import { IncomingMessage } from 'http';
import { getClient, logQueries } from '#faustjs/next';
import {
generatedSchema,
scalarsEnumsHash,
GeneratedSchema,
SchemaObjectTypes,
SchemaObjectTypesNames,
} from './schema.generated';
export const client = getClient({
GeneratedSchema,
SchemaObjectTypesNames,
SchemaObjectTypes,
schema: generatedSchema,
scalarsEnumsHash,
applyRequestContext: async (url, init) => {
const newInit = {
...init,
headers: {
...init.headers,
authorization: 'Basic YmxhbmtkZXhzaXRzdGc6OTMzODVlNjY=',
},
};
return { url, init: newInit };
},
});
export function serverClient(req) {
return getClient<GeneratedSchema, SchemaObjectTypesNames, SchemaObjectTypes>({
schema: generatedSchema,
scalarsEnumsHash,
context: req,
});
}
if (process.env.NODE_ENV === 'development') {
logQueries(client);
}
export * from './schema.generated';
The errors I'm getting when building it are among the following:
1:1 Error: There should be at least one empty line between import groups import/order
8:3 Error: GeneratedSchema not found in './schema.generated' import/named
9:3 Error: SchemaObjectTypes not found in './schema.generated' import/named
10:3 Error: SchemaObjectTypesNames not found in './schema.generated' import/named
I'm doing a PoC for Azure App Configuration. I created a free instance in our Azure env and added a couple of feature flags. The Issue I am having is with the JavaScript SDK when attempting to fetch feature flags from the App Configuration. Here's the snippet of code that I am using. (NOTE: the app is a React app w/ Typescript)
import { AppConfigurationClient } from '#azure/app-configuration';
import { GetConfigurationSettingResponse } from '#azure/app-configuration';
const appConfigClient = new AppConfigurationClient(process.env.REACT_APP_APPCONFIG_CONNECTION_STRING);
const requestFeatureFlags = async (): Promise<GetConfigurationSettingResponse> => {
const featureFlags = await appConfigClient.getConfigurationSetting({ key: '', label: 'my_feature_flags' });
console.log(featureFlags);
return featureFlags;
}
requestFeatureFlags().then((response) => { alert('I am here'); console.log(response) });
In the above I can see the request being made in the browser dev console and response with the feature flags, but the Promise is never returned from the await appConfigClient.getConfigurationSetting({ key: '', label: 'my_feature_flags' }); call. The console log of featureFlags yields this in the browser console ->
The alert inside the then() is never fired. So at this point I'm a loss lol. The SDK can be found here -> npm https://www.npmjs.com/package/#azure/app-configuration/v/1.1.0
If anyone else has come across this issue and help would be most appreciated!
What I have:
I adapted this example from Microsoft docs.
// functions.js
/**
* Get data
* #customfunction
* #returns {string[][]}
*/
async function getData() {
try {
const url = "https://api.example.com/some/objects/";
const token = await OfficeRuntime.storage.getItem("Token");
const authString = `Token ${token.toString()}`;
const response = await fetch(url, {
headers: { Authorization: authString }
});
if (!response.ok) {
throw new Error(response.statusText);
}
const jsonResponse = await response.json();
return jsonResponse.map(obj => {return [obj.id.toString(), obj.name]};
} catch (error) {
return [["ERROR", error.message]];
}
}
Added api.example.com to <AppDomains>
Item "Token" is present in OfficeRuntime.storage
Same API call with Postman works fine
The Add-In is not served from localhost (because of CORS reasons etc.)
What I get:
Because it is not developed locally it is very hard to debug ui-less custom functions. Therefore the only visible error I get so far, is the one I receive and return to Excel in the catch-block. It is an unhelpful error message: Network request failed
Can anyone help me with any suggestions?
Reason why it did not work was a known issue that custom functions run in a separate JavaScript runtime as described here. This runtime allows only CORS-safelisted request header because it lacks of CORS-Preflight -> therefore the Network request failed error with the Authorization header.
How I solved it:
Configure the Excel-Add-in to use a shared JavaScript runtime as described here.
Add <script src="functions.js"></script> just before the <\head> element in taskpane.html as described here.
Build the project npm run build
Clear the cache as described in the first two steps here.
Run Excel and load your Add-in.
I have a very simple node service exposing an endpoint aimed to use Server Send Events (SSE) connection and a very basic ReactJs client consuming it via EventSource.onmessage.
Firstly, when I set a debug point in updateAmountState (Chrome Dev) I can't see it evoked.
Secondly, I am getting net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK). According to https://github.com/aspnet/KestrelHttpServer/issues/1858 "ERR_INCOMPLETE_CHUNKED_ENCODING in chrome usually means that an uncaught exception was thrown from the application in the middle of writing to the response body". Then I checked the server side to see if I find any error. Well, I set break point in few places in server.js in both setTimeout(() => {... and I see it run periodically. I would expected each line to run once only. So it seems the front-end is trying permanently call the backend and getting some error.
The whole application, both front in ReactJs and the server in NodeJs can be found in https://github.com/jimisdrpc/hello-pocker-coins.
backend:
const http = require("http");
http
.createServer((request, response) => {
console.log("Requested url: " + request.url);
if (request.url.toLowerCase() === "/coins") {
response.writeHead(200, {
Connection: "keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache"
});
setTimeout(() => {
response.write('data: {"player": "Player1", "amount": "90"}');
response.write("\n\n");
}, 3000);
setTimeout(() => {
response.write('data: {"player": "Player2", "amount": "95"}');
response.write("\n\n");
}, 6000);
} else {
response.writeHead(404);
response.end();
}
})
.listen(5000, () => {
console.log("Server running at http://127.0.0.1:5000/");
});
frontend:
import React, { Component } from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
import { getInitialCoinsData } from "./DataProvider";
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: getInitialCoinsData()
};
this.columns = [
{
Header: "Player",
accessor: "player"
},
{
Header: "Amount",
accessor: "amount"
}
];
this.eventSource = new EventSource("coins");
}
componentDidMount() {
this.eventSource.onmessage = e =>
this.updateAmountState(JSON.parse(e.data));
}
updateAmountState(amountState) {
let newData = this.state.data.map(item => {
if (item.amount === amountState.amount) {
item.state = amountState.state;
}
return item;
});
this.setState(Object.assign({}, { data: newData }));
}
render() {
return (
<div className="App">
<ReactTable data={this.state.data} columns={this.columns} />
</div>
);
}
}
export default App;
The exception I can see on chrome:
So my straight question is: why I am getting ERR_INCOMPLETE_CHUNKED_ENCODING 200? Am I missing something in the backend or in the frontend?
Some tips may help me:
Why do I see websocket in oending status since I am not using websocket at all? I know the basic difference (websocket is two-way, from front to back and from back to front and is a diferent protocol while SSE run over http and is only back to front). But it is not my intention to use websocket at all. (see blue line in printscreen belllow)
Why do I see eventsource with 0 bytes and 236 bytes both failled. I understand that eventsource is exactly what I am trying to use when I coded "this.eventSource = new EventSource("coins");". (see read line in printscreen bellow)
Very strange at least for me, some time when I kill the serve I could see updateAmountState methond evoked.
If call the localhost:5000/coins in browser I can see the server answers the response (both json strings). Can I assume that I coded properly the server and the erros is something exclusevely in the frontend?
Here are the answers to your questions.
The websocket you see running is not related to the code you have posted here. It may be related to another NPM package that you are using in your app. You might be able to figure out where it is coming from by looking at the headers in the network request.
The most likely cause of the eventsource requests failing is that they are timing out. The Chrome browser will kill an inactive stream after two minutes of inactivity. If you want to keep it alive, you need to add some code to send something from the server to the browser at least once every two minutes. Just to be safe, it is probably best to send something every minute. An example of what you need is below. It should do what you need if you add it after your second setTimeout in your server code.
const intervalId = setInterval(() => {
res.write(`data: keep connection alive\n\n`);
res.flush();
}, 60 * 1000);
req.on('close', () => {
// Make sure to clean up after yourself when the connection is closed
clearInterval(intervalId);
});
I'm not sure why you are sometimes seeing the updateAmountState method being invoked. If you are not seeing it consistently, it's probably not a major concern, but it might help to clean up the setTimeouts in the case that the server stops before they complete. You can do this by declaring them as variables and then passing the variable names to a clearTimeout in a close event handler similar to what I did for the interval in the example in #2 above.
Your code is set up properly, and the error you are seeing is due to the Chrome browser timeouts. Use something like the code in answer #2 above if you want to stop the errors from happening.
I'm not a Node.js expert myself, but it looks like you miss "'Connection': 'keep-alive'" and a "\n" after that - i.e.:
response es.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
response.write('\n');
see https://jasonbutz.info/2018/08/server-sent-events-with-node/. Hope it works!
I've had trouble getting to make a simple request with node-soap and chronopost (shipping platform) soap api.
the first thing I did was to follow the basic node-soap example but it just fails miserably without any real USEFUL error from chronopost.
Here's what I have:
const soap = require('soap')
const client = await soap.createClientAsync(
'https://ws.chronopost.fr/shipping-cxf/ShippingServiceWS?wsdl'
)
client.shippingV6(...somedata, (err, result) => {
if (err) {
return handleErr(); // it always fails
}
handleResult();
})
After multiple attempt it seems like chronopost api uses special root attributes (who knows why) and you need to craft the options on node-soap that actually fit their needs (yay..)
Here's what works for me
const createClientShippingServiceWS = async () => {
const wsdlOptions = {
envelopeKey: 'soapenv',
overrideRootElement: {
namespace: 'cxf',
xmlnsAttributes: [
{
name: 'xmlns:cxf',
value: 'http://cxf.shipping.soap.chronopost.fr/'
}
]
}
}
return await soap.createClientAsync(
'https://ws.chronopost.fr/shipping-cxf/ShippingServiceWS?wsdl',
wsdlOptions
)
}
Also what's the point of getting the wsdl if node-soap can't event figure how to make the response??
Thanks chronopost for being stuck in 2008