I am trying to integrate Opentelemetry (Otl) in my Angular application to trace the frontend calls. Everything works fine and I am able to see the calls in the Zipkin.
But the only problem is that it is showing it as "unknown_service" in the Zipkin interface.
Below is my entire Angular code and Zipkin screenshot as well. This is just a sample application. But my requirement is that I am going to integrate the Opentelemetry code in the http interceptor so that it will be easy to maintain at one place instead of every service call. Also service.name should be passed dynamically so that it will be traced in Zipkin.
How can I add a service name before it gets called?
import { Component, OnInit } from '#angular/core';
import {ZipkinServicesService} from './zipkin-services.service';
// Opentelemetry components
import { context, trace } from '#opentelemetry/api';
import { ConsoleSpanExporter, SimpleSpanProcessor } from '#opentelemetry/tracing';
import { WebTracerProvider } from '#opentelemetry/web';
import { XMLHttpRequestInstrumentation } from '#opentelemetry/instrumentation-xml-http-request';
import { ZoneContextManager } from '#opentelemetry/context-zone';
import { CollectorTraceExporter } from '#opentelemetry/exporter-collector';
import { B3Propagator } from '#opentelemetry/propagator-b3';
import { registerInstrumentations } from '#opentelemetry/instrumentation';
import { ZipkinExporter } from '#opentelemetry/exporter-zipkin';
#Component({
selector: 'app-zipkin-integration',
templateUrl: './zipkin-integration.component.html',
styleUrls: ['./zipkin-integration.component.scss']
})
export class ZipkinIntegrationComponent implements OnInit {
respData: string;
webTracerWithZone;
constructor(
public zipkinService: ZipkinServicesService,
) {
const providerWithZone = new WebTracerProvider();
const options = {
url: 'http://localhost:9411/api/v2/spans',
serviceName: 'interceptor-example',// This is NOT working.
}
const exporter = new ZipkinExporter(options);
const zipKinProcessor = new SimpleSpanProcessor(exporter);
providerWithZone.addSpanProcessor(zipKinProcessor);
providerWithZone.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
providerWithZone.addSpanProcessor(new SimpleSpanProcessor(new CollectorTraceExporter()));
providerWithZone.register({
contextManager: new ZoneContextManager(),
propagator: new B3Propagator(),
});
registerInstrumentations({
instrumentations: [
new XMLHttpRequestInstrumentation({
ignoreUrls: [/localhost:8090\/sockjs-node/],
propagateTraceHeaderCorsUrls: [
'https://httpbin.org/post',
],
}),
],
});
this.webTracerWithZone = providerWithZone.getTracer('example-tracer-web');
}
ngOnInit(): void {
}
zipGet (){
let i = 10;
const span1 = this.webTracerWithZone.startSpan(`files-series-info-${i}`);
let postData = [{
no : 2,
emp : 3
}];
context.with(trace.setSpan(context.active(), span1), () => {
this.zipkinService.httpGet(postData).subscribe( (data: any) => {
this.respData = data;
// Opentelemetry after response.
trace.getSpan(context.active()).addEvent('fetching-span1-completed');
span1.end();
});
});
}
zipPost (){
let postData = [{
no : 1,
emp : 2
}];
let i = 10;
const span1 = this.webTracerWithZone.startSpan(`files-series-info-${i}`);
context.with(trace.setSpan(context.active(), span1), () => {
this.zipkinService.httpPost(postData).subscribe( (data: any) => {
this.respData = data;
// Opentelemetry after response.
trace.getSpan(context.active()).addEvent('fetching-span1-completed');
span1.end();
});
});
}
}
Service name must be set via resource as per the specification. I am not sure which version of js libs you are using. This should get you the service name.
import { Resource } from '#opentelemetry/resources';
import { ResourceAttributes } from '#opentelemetry/semantic-conventions'
...
...
const provider = new WebTracerProvider({
resource: new Resource({
[ResourceAttributes.SERVICE_NAME]: "interceptor-example"
}),
});
use providerConfig to set service name. follow code set service name to "SPA Test".
import { Resource } from '#opentelemetry/resources';
import { SemanticResourceAttributes } from '#opentelemetry/semantic-conventions'
import { BatchSpanProcessor } from '#opentelemetry/sdk-trace-base';
import { WebTracerProvider } from '#opentelemetry/sdk-trace-web';
import { ZipkinExporter, ExporterConfig } from '#opentelemetry/exporter-zipkin';
const providerConfig = {
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: "SPA Test"
}),
};
const provider = new WebTracerProvider(providerConfig);
const zipkinOptions: ExporterConfig = {
url: "http://localhost:9411/api/v2/spans"
};
const exporter = new ZipkinExporter(zipkinOptions);
const zipkinProcessor = new BatchSpanProcessor(exporter);
provider.addSpanProcessor(zipkinProcessor);
provider.register();
var tracer = provider.getTracer(CustomersComponent.name, "0.1.0");
var span = tracer.startSpan(CustomersComponent.name);
console.info(span);
span.end();
Related
I am in the process of converting a AngularJS app to Angular 7 in TypeScript.
I am having some problems with converting some complex nested promises to Observables.
Here is an example of the code I'm dealing with:
signup.component.js
function SomethingSignupController(somethingApplication) {
function activate() {
getApplication();
}
function getApplication() {
vm.getFromServer = false;
vm.promises = [];
SomethingRepo.get().then(function(application) {
vm.getFromServer = true;
vm.application = application;
vm.promises.push(Something.getCompany().then(function(company) {
vm.company = company;
if (vm.company.structure === ‘more_25’) {
return SomethingRepo.getAllOwners().then(function(owners) {
vm.owners = owners;
for(var i = 0; i < vm.owners.length; i++) {
vm.promises.push(getOwnerFiles(vm.owners[i]));
}
}
}
}
vm.promises.push(SomethingRepo.getSomethingOne().then(function(somethingOne) {
vm.somethingOne = somethingOne;
}
vm.promises.push(SomethingRepo.getSomethingTwo().then(function(somethingTwo) {
vm.somethingTwo = somethingTwo;
}
vm.promises.push(SomethingRepo.getSomethingThree().then(function(somethingThree) {
vm.somethingThree = somethingThree;
}
/* and a few more like the above */
$q.all(vm.promises).then(function(){
postGet();
}).finally(function() {
vm.promises = [];
});
}
}
function postGet() {
/* does something with the data acquired from SomethingRepo */
}
/* when an application is send */
function send() {
somethingApplication.promises = [];
somethingApplication.errors = [];
if (vm.getFromServer) {
update();
} else {
create();
}
}
function update() {
somethingApplication.promises.push(SomethingRepo.update(vm.application).then(angular.noop, function(error) {
somethingApplication.parseErrors(error, ‘Some error’);
}));
patchInfo();
}
function create() {
}
function patchInfo() {
somethingApplication.promises.push(SomethingRepo.patchAccount(vm.account).then(angular.noop, function(error) {
somethingApplication.parseErrors(error, ‘Account error: ‘);
}
/* a few more patches */
$q.all(somethingApplication.promises).then(function() {
/* display dialog */
}, angular.noop).finally(function() {
postGet();
somethingApplication.promises = [];
if (somethingApplication.errors.length >= 1) {
vm.errors = somethingApplication.errors;
}
});
}
}
somethingApplication.service.js
function somethingApplication(SomethingRepo) {
var promises = [], errors = [];
var service = {
promises: promises;
errors = errors;
parseErrors: parseErrors;
};
return service;
function parseErrors(error, base_string) {
angular.forEach(error.data.erros, function(value_params, key_params) {
this.errors.push(base_string + ‘ ‘ + key_params.replace(/_/g, ‘ ‘) + ‘ ‘ + value_params);
}, this);
}
}
somethingRepo.js
function SomethingRepo(Server) {
function get() {
return Server.get(‘/something/application’, null, {noGlobal: true});
}
}
I have reduced the files, but they consist of more code like this.
The point of the controller is to create or update an application for another website. On my website I have a form of fields corresponding to the form on the other website. If you already have filed for an application, but want to update it, the info you already filed are loaded from the other website.
The problem is, in order to create or update an application, a lot of different endpoints requested og posted to.
In AngularJS I store the promises from each request and run them asynchronously in the end. In TypeScript and Angular I want to use Observables and subscribe to the data change.
How do I get started? How do I subscribe to an Observable the requires parameters from another Observable? Any advice how to proceed?
Here's an example demonstrating how you can easily use observables in your scenario -
Your service would be something like this -
import { Injectable } from '#angular/core';
import { AppConstants } from '../../app.constants';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class ExampleService {
constructor(private appconstants: AppConstants, private http: HttpClient) { }
get() {
return this.http.get(this.appconstants.apiUrl);
}
getSomethingOne() {
return this.http.get(this.appconstants.apiUrl1);
}
getSomethingTwo() {
return this.http.get(this.appconstants.apiUrl2);
}
}
Then simply use it in your component as follows -
import { Component } from '#angular/core';
import { forkJoin } from 'rxjs';
import { ExampleService } from '../services/example.service';
#Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
data;
dataOne;
dataTwo;
constructor(private exampleService: ExampleService) { }
getApplication() {
const combined = forkJoin([
this.exampleService.get(),
this.exampleService.getSomethingOne(),
this.exampleService.getSomethingTwo()
]);
combined.subscribe(res => {
this.data = res[0];
this.dataOne = res[1];
this.dataTwo = res[2];
});
}
}
I'm working on Angular-calendar.
I want to wait for HTTP response before component loads in Angular-5.
Calendar component.ts :-
I want to show data from service in this.totallist,
events: CalendarEvent[] = this.totallist; // I have show data here which is fetched from database.
ngOnInit() {
this.eventlist.getEvents().subscribe(
data => {
console.log(data);
let MyObj = JSON.parse(data);
let event_data_1 = [];
let outage_list = MyObj.outageList;
console.log(outage_list);
let startdate = outage_list[0].actualStartDateTime;
let enddate = outage_list[0].actualStartEndTime;
// console.log(startdate);
let converted_startdate = this.cleanDate('/Date('+startdate+')/');
let converted_enddate = this.cleanDate('/Date('+enddate+')/');
console.log(converted_startdate);
console.log(converted_enddate);
for(let i=0;i<1;i++)
{
let sub_object = {
start: new Date(),
end: new Date(),
title: 'A 3 day event',
color: colors.red,
actions: this.actions
}
event_data_1.push(sub_object);
}
this.totallist = event_data_1;
console.log(this.totallist);
return this.totallist;
}
);
// this.column_data = column_data;
}
service.ts :-
import { Http, Response } from '#angular/http';
import { Injectable } from '#angular/core';
import { Resolve, ActivatedRouteSnapshot } from '#angular/router';
import { Observable } from 'rxjs/Rx';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Injectable()
export class ApiNewsResolver {
getEvents(): Observable<any>{
return this.http.get('http://localhost:8080/MacromWeb/ws/Calendar').map(data => data['_body']);
}
constructor(private http: Http) {
}
}
Now I want to load API service first and then load component. How can I achieve this task in Angular-5 ?
If any need I can show my whole component code.
Just store your data inside the subscribe:
events: CalendarEvent[]; // I have show data here which is fetched from database.
ngOnInit() {
this.eventlist.getEvents().subscribe(
data => {
console.log(data);
let MyObj = JSON.parse(data);
let event_data_1 = [];
let outage_list = MyObj.outageList;
console.log(outage_list);
let startdate = outage_list[0].actualStartDateTime;
let enddate = outage_list[0].actualStartEndTime;
// console.log(startdate);
let converted_startdate = this.cleanDate('/Date('+startdate+')/');
let converted_enddate = this.cleanDate('/Date('+enddate+')/');
console.log(converted_startdate);
console.log(converted_enddate);
for(let i=0;i<1;i++)
{
let sub_object = {
start: new Date(),
end: new Date(),
title: 'A 3 day event',
color: colors.red,
actions: this.actions
}
event_data_1.push(sub_object);
}
this.events = event_data_1;
}
);
// this.column_data = column_data;
}
Your component must be loaded, but you can load and unload HTML parts with *ngIf directive. Just check if your array is empty.
More on that here.
Also, you may wanna check out Angular Interceptors
I have a Websocket.JS that is defined as the following :
class Websocket extends Component {
constructor(props) {
super(props);
const url = Config.ws + Config.baseEndPoint + Config.pen;
const protocol = this.props.protocol;
const doesLogging = (this.props.doesLogging === "true");
this.state = {"url": url,
"protocol": protocol,
"socket": new WebSocket(url, protocol),
"doesLogging": doesLogging};
};
onOpen() {
}
onMessage(msg) {
}
onClose() {
}
render() {
return (
<div id="websocket"></div>
);
}
}
export default Websocket;
And use it in the app.js like this:
import Websocket from './services/websocket';
<Websocket handleMessage={(msg) => this.messageReceived(msg.data)} />
However, I want to use it just as a module without it being represented like an HTML element. Is there any possible I can do that ?
I do not think that interaction via websockets must be presented in your code by using any kind of the UI library. Therefore:
class YourWebsocket{
constructor(protocol, url, enableLogging) {
this.url = url;
this.protocol = protocol;
this.loggingEnabled = enableLogging;
this.socket = new WebSocket(url, protocol);
this.socket.onopen = (ev) => this.onOpen();
// Assign more handlers here
}
onOpen() {
}
onMessage(msg) {
}
onClose() {
}
send(data, callback)
{
var listener = (e) =>
{
var reply = e.data;
if(replyIsForOurRequest) //Here you should come up with the logic to decide if this is reply for your request
{
this.socket.removeEventListener("message", listener);
callback(reply);
}
};
this.socket.addEventListener("message", listener);
this.socket.send(data);
}
}
export default YourWebsocket;
And in your app.js somewhere in componentDidMount method:
import YourWebsocket from './services/YourWebsocket';
class App extends React.Component
{
componentDidMount()
{
this.webSocket = new YourWebsocket();
//...
}
}
I am new to Angular2 and working on a webapp. In this webapp, I would like to execute three different calls to a REST API. To test this, i "mocked" the API calls against three files, containing three JSON objects that should return the data. It is not working as expected, as I am getting the following error message in the developer console:
ncaught (in promise): Error: Error in ./Search class Search - inline template:4:0 caused by: Maximum call stack size exceeded
Please see the following code:
The template -
search.template.html:
<search>
<h1>Angular2 HTTP Demo App</h1>
<h2>Foods</h2>
<ul>
<li *ngFor="let food of foods"><input type="text" name="food-name" [(ngModel)]="food.name"><button (click)="updateFood(food)">Save</button> <button (click)="deleteFood(food)">Delete</button></li>
</ul>
<p>Create a new food: <input type="text" name="food_name" [(ngModel)]="food_name"><button (click)="createFood(food_name)">Save</button></p>
<h2>Books and Movies</h2>
<h3>Books</h3>
<ul>
<li *ngFor="let book of books">{{book.title}}</li>
</ul>
<h3>Movies</h3>
<ul>
<li *ngFor="let movie of movies">{{movie.title}}</li>
</ul>
</search>
The search service, which executes the API calls -
search.service.ts:
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map'
import { Observable } from 'rxjs/Observable';
import { Http, Response, Headers, RequestOptions } from "#angular/http";
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/observable/of';
#Injectable()
export class SearchService {
constructor(private http:Http) {
}
// Uses http.get() to load a single JSON file
getFoods() {
return this.http.get('./food.json').map((res:Response) => res.json());
}
// Uses Observable.forkJoin() to run multiple concurrent http.get() requests.
// The entire operation will result in an error state if any single request fails.
getBooksAndMovies() {
return Observable.forkJoin(
this.http.get('./books.json').map((res:Response) => res.json()),
this.http.get('./movies.json').map((res:Response) => res.json())
);
}
createFood(food) {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
let body = JSON.stringify(food);
// Note: This is only an example. The following API call will fail because there is no actual API to talk to.
//return this.http.post('/api/food/', body, headers).map((res:Response) => res.json());
}
updateFood(food) {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
let body = JSON.stringify(food);
// Note: This is only an example. The following API call will fail because there is no actual API to talk to.
//return this.http.put('/api/food/' + food.id, body, headers).map((res:Response) => res.json());
}
deleteFood(food) {
// Note: This is only an example. The following API call will fail because there is no actual API to talk to.
//return this.http.delete('/api/food/' + food.id);
}
}
The search component class -
search.component.ts:
import {Component, Output, EventEmitter, OnInit} from '#angular/core';
import { SearchService } from '../search/search.service';
import { AppState } from '../app.service';
import { HomeService } from '../home/home.service';
import { Injectable } from '#angular/core';
import { URLSearchParams, Jsonp } from '#angular/http';
import { Observable } from 'rxjs/Observable';
#Component({
// The selector is what angular internally uses
// for `document.querySelectorAll(selector)` in our index.html
// where, in this case, selector is the string 'home'
selector: 'search', // <home></home>
// We need to tell Angular's Dependency Injection which providers are in our app.
providers: [
HomeService,
SearchService
],
//
directives: [CORE_DIRECTIVES],
// Our list of styles in our component. We may add more to compose many styles together
//styleUrls: [ ],
// Every Angular template is first compiled by the browser before Angular runs it's compiler
templateUrl: 'search.template.html'
})
export class Search {
public foods;
public books;
public movies;
public food_name;
constructor(private _searchService: SearchService) { }
ngOnInit() {
this.getFoods();
this.getBooksAndMovies();
}
getFoods() {
this._searchService.getFoods().subscribe(
// the first argument is a function which runs on success
data => { this.foods = data},
// the second argument is a function which runs on error
err => console.error(err),
// the third argument is a function which runs on completion
() => console.log('done loading foods')
);
}
getBooksAndMovies() {
this._searchService.getBooksAndMovies().subscribe(
data => {
this.books = data[0]
this.movies = data[1]
}
// No error or completion callbacks here. They are optional, but
// you will get console errors if the Observable is in an error state.
);
}
/*createFood(name) {
let food = {name: name};
this._searchService.createFood(food).subscribe(
data => {
// refresh the list
this.getFoods();
return true;
},
error => {
console.error("Error saving food!");
return Observable.throw(error);
}
);
}
updateFood(food) {
this._searchService.updateFood(food).subscribe(
data => {
// refresh the list
this.getFoods();
return true;
},
error => {
console.error("Error saving food!");
return Observable.throw(error);
}
);
}
deleteFood(food) {
if (confirm("Are you sure you want to delete " + food.name + "?")) {
this._searchService.deleteFood(food).subscribe(
data => {
// refresh the list
this.getFoods();
return true;
},
error => {
console.error("Error deleting food!");
return Observable.throw(error);
}
);
}
}*/
/*
#Output() addVehicle = new EventEmitter();
// Set our default values
localState = { value: '' };
vehicles= [
{
id: 0,
name: 'TEST'
}
];
// TypeScript public modifiers
constructor(public appState: AppState, private homeService: HomeService) {
// this.appState.set('value', value);
}
add(_event) {
console.log('adding ', _event);
this.addVehicle.emit({
value: _event
});
}
constructor(private _dataService: DataService) { }
ngOnInit() {
this.getAllItems();
}
private getAllItems(): void {
this._dataService
.GetAll()
.subscribe((data:MyTypedItem[]) => this.myItems = data,
error => console.log(error),
() => console.log('Get all Items complete'));
}
ngOnInit() {
console.log('hello `Home` component');
}
*/
}
movies.json:
[
{ "title": "Ghostbusters" },
{ "title": "Star Wars" },
{ "title": "Batman Begins" },
{ "title": "Bourne Identity" },
{ "title": "Bourne Identity 2" }
]
food.json:
[
{ "id": 1, "name": "Donuts" },
{ "id": 2, "name": "Pizza" },
{ "id": 3, "name": "Sushi" }
]
books.json:
[
{ "title": "Hitchhiker's Guide to the Galaxy" },
{ "title": "The Fellowship of the Ring" },
{ "title": "Moby Dick" }
]
index.ts:
export * from './search.component';
I have absolutely no other guess why this is failing than a indefinite function in the code, but I cannot figure out, where. Therefore, any hints and help would be very much appreciated! Thanks!
From your search.template.html, remove <search> tags. Because of your search tag in html template, it is going recursive. Hence, you are receiving Maximum call stack size exceeded error.
Keep template like this-
<h1>Angular2 HTTP Demo App</h1>
<h2>Foods</h2>
<ul>
<li *ngFor="let food of foods"><input type="text" name="food-name" [(ngModel)]="food.name"><button (click)="updateFood(food)">Save</button> <button (click)="deleteFood(food)">Delete</button></li>
</ul>
<p>Create a new food: <input type="text" name="food_name" [(ngModel)]="food_name"><button (click)="createFood(food_name)">Save</button></p>
<h2>Books and Movies</h2>
<h3>Books</h3>
<ul>
<li *ngFor="let book of books">{{book.title}}</li>
</ul>
<h3>Movies</h3>
<ul>
<li *ngFor="let movie of movies">{{movie.title}}</li>
</ul>
See if this helps.
There are lot of documentation and examples on how to convert Angular 1 services and factories to Angular2 but I couldnt find anything on how to convert a ng1 provider to something equivalent in ng2.
Example provider
function AlertService () {
this.toast = false;
this.$get = getService;
this.showAsToast = function(isToast) {
this.toast = isToast;
};
getService.$inject = ['$timeout', '$sce'];
function getService ($timeout, $sce) {
var toast = this.toast,
alertId = 0, // unique id for each alert. Starts from 0.
alerts = []
return {
factory: factory,
add: addAlert
};
function factory(alertOptions) {
var alert = {
type: alertOptions.type,
msg: $sce.trustAsHtml(alertOptions.msg),
id: alertOptions.alertId,
toast: alertOptions.toast
};
alerts.push(alert);
return alert;
}
function addAlert(alertOptions) {
alertOptions.alertId = alertId++;
var alert = this.factory(alertOptions);
return alert;
}
}
}
angular
.module('angularApp', [])
.provider('AlertService', AlertService);
What would be the correct equivalent for this in Angular 2?
Ok so finally we figured it out thanks to https://github.com/jhipster/generator-jhipster/issues/3664#issuecomment-251902173
Here is the Service in NG2
import {Injectable, Sanitizer, SecurityContext} from '#angular/core';
#Injectable()
export class AlertService {
private alertId: number;
private alerts: any[];
constructor(private sanitizer: Sanitizer, private toast: boolean) {
this.alertId = 0; // unique id for each alert. Starts from 0.
this.alerts = [];
}
factory(alertOptions): any {
var alert = {
type: alertOptions.type,
msg: this.sanitizer.sanitize(SecurityContext.HTML, alertOptions.msg),
id: alertOptions.alertId,
toast: alertOptions.toast
};
this.alerts.push(alert);
return alert;
}
addAlert(alertOptions, extAlerts): any {
alertOptions.alertId = this.alertId++;
var alert = this.factory(alertOptions);
return alert;
}
isToast(): boolean {
return this.toast;
}
}
and here is the provider for the service
import { Sanitizer } from '#angular/core';
import { AlertService } from './alert.service';
export function alertServiceProvider(toast?: boolean) {
// set below to true to make alerts look like toast
let isToast = toast ? toast : false;
return {
provide: AlertService,
useFactory: (sanitizer: Sanitizer) => new AlertService(sanitizer, isToast),
deps: [Sanitizer]
}
}
Now you need to call the alertServiceProvider method in the provider declaration of your module.
#NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
...
alertServiceProvider()
],
exports: [
...
]
})
export class SharedCommonModule {}
The code is part of the JHipster project and you can browse actual templates here