This is app.js file;
const MyTest = require('./testApp');
const App = new MyTest();
App.on('ready', () => {
console.log('Hello World');
});
Package.json file. (in) ./testApp/package.json;
{
...
"main": "main.js"
...
}
This is ./testApp/main.js file;
const EventEmitter = require('events'); // node event module
class Client extends EventEmitter {
constructor() {
super();
this.sendReady();
}
sendReady() {
this.emit('ready');
}
}
module.exports = Client;
If "App" is successfully installed in the app.js file, I want the console to write " Hello World". I've changed a few times this content because I want to more simple.
I want to trigger App.on in app.js from ./testApp/main.js
If I define a function in the constructor, I can get it by calling "App." In this case this = App
Then, why don't emit anything?
Note: this app files working locally.
The emit call is in Client's constructor, i.e. it gets called when you invoke
const helper = new pulseHelper.Client();
However, you only register the ready handler after that. EventEmitters don't buffer their events; only listeners registered during the emit call will ever be able to act on the events.
EDIT: to clarify what I'm talking about, see this example:
const EventEmitter = require('events');
class Client extends EventEmitter {
constructor() {
super();
this.emit('ready', 'I\'m Ready!');
}
boom() {
this.emit('ready', 'Boom!');
}
emit(event, payload) {
console.log('Emitting', event, ':', payload);
super.emit(event, payload);
}
}
const c = new Client();
c.on('ready', (payload) => console.log(payload));
c.boom();
This prints (with annotations):
# client constructed, it emits an event but no one is listening.
# however the "debug" emit() implementation prints this:
Emitting ready : I'm Ready!
# the listener has been registered, now let's call .boom()!
# the "debug" emit() implementation prints this:
Emitting ready : Boom!
# and the handler prints the following:
Boom!
Related
I need to access the fileHandler object of my logger so I can flush the buffer to the file.
This is my program:
import * as log from "https://deno.land/std#0.75.0/log/mod.ts"
import { Application } from "https://deno.land/x/oak#v6.3.1/mod.ts";
const app = new Application()
const port = 7001
await log.setup({
handlers:{
file: new log.handlers.FileHandler("DEBUG",{
filename: "logger.log",
formatter: lr => {
return `${lr.datetime.toISOString()} [${lr.levelName}] ${lr.msg}`
}
})
},
loggers: {
default: {
level: "DEBUG",
handlers: ["file"]
}
}
})
const logger = log.getLogger()
logger.debug("hi there")
app.use((ctx) => {
ctx.response.body = 'Hi there'
})
console.log(`listening on port ${port}`)
app.listen({ port })
My problem is that the log message is never being written to file.
If I remove the last line ( app.listen() ) it Does write to the file because the process ends.
But if I leave it listening process never ends so the log buffer is never flushed.
If I interrupt the process with Ctrl-C it doesn't write it either
Documentation (https://deno.land/std#0.75.0/log/README.md) says I can force log flush using the flush method from FileHandler. But I don't know how to access the fileHandler object.
So I've tried this:
const logger = log.getLogger()
logger.debug("hi there")
logger.handlers[0].flush()
And it works! but only as javascript, NOT as typescript
As typescript I get this error:
error: TS2339 [ERROR]: Property 'flush' does not exist on type 'BaseHandler'.
logger.handlers[0].flush()
Well, I found a solution.
I just have to import the FileHandler class and cast my handler down from BaseHandler to FileHandler.
So I added this line among the imports:
import { FileHandler } from "https://deno.land/std#0.75.0/log/handlers.ts"
And then after creating the logger:
logger.debug("hi there")
const fileHandler = <FileHandler> logger.handlers[0]
fileHandler.flush()
Looks a little weird, I still guess there must be less quirky / more semantic solution for this. But it works ok.
Let us just recap with the help of Santi's answer.
In my experience logs in file work fine in an ending program. I mean a program which dies by itself or with Deno.exit(0). Problem occurs in a never ending loop. In this case logs don't append in their files. Below is how to overcome this situation :
// dev.js : "I want my logs" example
import {serve} from "https://deno.land/std#0.113.0/http/server_legacy.ts";
import * as log from "https://deno.land/std#0.113.0/log/mod.ts";
// very simple setup, adapted from the official standard lib https://deno.land/std#0.113.0/log
await log.setup({
handlers: {
file: new log.handlers.FileHandler("WARNING", {
filename: "./log.txt",
formatter: "{levelName} {msg}",
}),
},
loggers: {
default: {
level: "DEBUG",
handlers: ["file"],
},
},
});
// here we go
let logger;
logger = log.getLogger();
logger.warning('started');
const fileHandler = logger.handlers[0];
await fileHandler.flush(); // <---- the trick, need to flush ! Thanks Santi
// loop on requests
const srv = serve(`:4321`);
for await (const request of srv) {
request.respond({body: 'bonjour', status: 200});
logger.warning('hit !');
fileHandler.flush(); // <---- flush again
}
Run with
$ deno run -A dev.js
And check the file log.txt with the following trigger
$ curl localhost:4321
This is a very low tech, problably adding important delay to the process. The next level will be to fire a time event to flush every minute or so.
I need to make a HTTP call in my test and I need to verify my observeable.
When I debug the code, the HTTP call is getting called in service, but in my test, it fails and it says http is undefined, but while debugging I'm able to see the http observal in console.
//service.ts file
//imports modles goes here
export class DataService {
private api = this.configService.config.apiUrl;
constructor(private http: HttpClient,
private configService: AppConfigService,
private errorService: ErrorService)
{
}
public getCustomerList(): Observable<IUserResponse> {
return this.http.get<IUserResponse>`${this.api}/v1/getusers/users`);
}
}
my test file serviec.specs.ts
describe('OnboardingService', () => {
beforeEach(() => {
const httpClient = new HttpClient(new HttpXhrBackend({ build: () => new XMLHttpRequest() }));
appConfigService= new AppConfigService(httpClient);
appConfigService.config = { apiUrl:"https://test-test-getuser.json"}
erroService = new ErrorService();
service = new Dataservice(httpClient, appConfigService,erroService);
it('should return an Observable<Iuser[]>', done => {
service.getCustomerList().subscribe(response => {
expect(response.users).toBeGreaterThan(0);
done();
});
});
})
Try this
this.http.get(`${this.apiurl}/v1/getuser`).subscribe((response)=>{console.log(response.user)})
Grey right arrow shows your code to execute. Left grey arrow below shows the result of execution. Console log has no arrow in dev tools.
The result of execution .subscribe() is a Subscriber as you can notice in your screenshot. Your code just doesn't execute function you passed to .subscribe().
So your problem is with your endpoint probably. Check the network tab in your dev tools.
I have a package (Let's say PACKAGE_A) written to do some tasks. Then it is required by PACKAGE_B. PACKAGE_A is a node script for some automation work. It has this Notifier module to create and export an EventEmitter. (The Whole project is a Monorepo)
const EventEmitter = require('events');
let myNotifier = new EventEmitter();
module.exports = myNotifier;
So in some functions in PACKAGE_A it emits event by requiring myNotifier, and also in the index.js of PACKAGE_A, I export functions (API exposed to the other packages) and the myNotifier by requiring it again.
const myNotifier = require('./myNotifier);
const func1 = () => {
// some function
return something;
}
module.exports = {func1, myNotifier}
Then I import the PACKAGE_A in PACKAGE_B and use the API functions exposed with the notifier. PACKAGE_B is an electron app with a React UI.
Below is how the program works.
I have a console output window in the electron app (React UI, UI_A). <= (keep this in mind)
When I click a button in UI_A it fires a redux action (button_action). Inside the action, a notification is sent to an event which is listened in the electron code using ipcRenderer.
ipcRenderer.send('button-clicked', data); // <= this is not the full code of the action. It's bellow.
Then in the electron code (index.js), I require another file (UI_A_COM.js which houses the code related to UI_A in electron side). The reason is code separation. Here's part of the code in index.js related to the electron.
const ui_a_com = require('./electron/UI_A_COM');
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
resizable: false,
});
mainWindow.loadURL('http://localhost:3000');
const mainMenu = Menu.buildFromTemplate(menuTemplate);
ui_a_com (mainWindow);
};
Alright. Then in UI_A_COM.js, I listen to that triggered event button-clicked.
ipcMain.on('button-clicked', someFunction);
which runs the code from PACKAGE_A and return a result. So now when PACKAGE_A runs, it emits some events using myNotifier. I listen to them in the same file (UI_A_COM.js), and when those events are captured, I again send some events to React UI, which is subscribed when button_action fired.
myNotifier.on('pac_a_event_a', msg => {
mainWindow.webContents.send('ui_event_a', msg); // code in `UI_A_COM.js`
});
Here's the full code for the action. (Did not provide earlier because you'll get confused)
export const buttonAction = runs => {
return dispatch => {
ipcRenderer.send('button-clicked', data);
ipcRenderer.on('ui_event_a', (event, msg) => {
dispatch({ type: SOME_TYPE, payload: { type: msg } });
});
};
};
This will show the msg in the UI_A console.
So this is the task I'm doing. The problem is when I click the button; it works perfectly for the first time. But when I click the button on the second time, it received two messages. Then when I click the button again, three messages and it keeps growing. (but the functions in the PACKAGE_A only executes one time per button press).
Let's say the message from PACKAGE_A emitted is 'Hello there' per execution.
When I press the button 1st time a perfect result => Hello there, When I click the button again => Hello there Hello there, When I click it again => Hello there Hello there Hello there.
It's kept so on. I think my implementation of EventEmitter has some flows. So why it's happening like this? Is it EventEmitter or something else? What am I doing wrong here?
By default the electron-react boilerplate doesnt define the ipcRenderer.removeAllListeners method. So you have to first go to the main/preloads.ts file and add them :
removeListener(channel: string, func: (...args: unknown[]) => void) {
ipcRenderer.removeListener(channel, (_event, ...args) => func(...args));
},
removeAllListeners(channel: string) {
ipcRenderer.removeAllListeners(channel);
},
Then go to the renderer/preload.t.s declaration file and add the declarations too:
removeListener(
channel: string,
func: (...args: unknown[]) => void
): void;
removeAllListeners(channel: string): void;
After that make sure to clean all listeners in the cleanup function of your useEffects each time you listen to an event fired. This will prevent multiple firing.
useEffect(() => {
window.electron.ipcRenderer.on('myChannel', (event, arg) => {
// do stuffs
});
return () => {
window.electron.ipcRenderer.removeAllListeners('myChannel');
};
});
I think you should return a function that call ipcRenderer.removeAllListeners() in your component's useEffect().
Because every time you click your custom button, the ipcRenderer.on(channel, listener) is called, so you set a listener to that channel agin and agin...
Example:
useEffect(() => {
electron.ipcRenderer.on('myChannel', (event, arg) => {
dispatch({ type: arg });
});
return () => {
electron.ipcRenderer.removeAllListeners('myChannel');
};
});
Sentry by defaults has integration for console.log to make it part of breadcrumbs:
Link: Import name: Sentry.Integrations.Console
How can we make it to work for bunyan logger as well, like:
const koa = require('koa');
const app = new koa();
const bunyan = require('bunyan');
const log = bunyan.createLogger({
name: 'app',
..... other settings go here ....
});
const Sentry = require('#sentry/node');
Sentry.init({
dsn: MY_DSN_HERE,
integrations: integrations => {
// should anything be handled here & how?
return [...integrations];
},
release: 'xxxx-xx-xx'
});
app.on('error', (err) => {
Sentry.captureException(err);
});
// I am trying all to be part of sentry breadcrumbs
// but only console.log('foo'); is working
console.log('foo');
log.info('bar');
log.warn('baz');
log.debug('any');
log.error('many');
throw new Error('help!');
P.S. I have already tried bunyan-sentry-stream but no success with #sentry/node, it just pushes entries instead of treating them as breadcrumbs.
Bunyan supports custom streams, and those streams are just function calls. See https://github.com/trentm/node-bunyan#streams
Below is an example custom stream that simply writes to the console. It would be straight forward to use this example to instead write to the Sentry module, likely calling Sentry.addBreadcrumb({}) or similar function.
Please note though that the variable record in my example below is a JSON string, so you would likely want to parse it to get the log level, message, and other data out of it for submission to Sentry.
{
level: 'debug',
stream:
(function () {
return {
write: function(record) {
console.log('Hello: ' + record);
}
}
})()
}
I am trying to test an angular2 application. I have a login form, which uses an observable to send data to the backend:
doLogin() {
this.usersService.login(this.model)
.subscribe((data) => {
console.log("In observable: " + data.isSuccess);
if (!data.isSuccess) {
this.alerts.push({});
}
});
}
In tests I am adding a spy on the service function, which returns observable, so that component can work on it:
usersService.login.and.returnValue(Observable.of(
<LoginResponse>{
isSuccess: true
}));
When everything is ready, I dispatch an event on submit button, which triggers doLogin function in component:
submitButton.dispatchEvent(new Event("click"));
fixture.detectChanges();
It works correctly. Unfortunately, when I check if usersService.login has been called in the test:
expect(usersService.login).toHaveBeenCalled();
I get an error, because the observable didn't finish and login has not been called yet.
How should I make sure, I check my spy after observable has finished?
I don't know how you configure the service on the component but it works for me when I override providers of the component created from TestComponentBuilder.
Let's take a sample. I have a service that returns a list of string:
import {Observable} from 'rxjs/Rx';
export class MyService {
getDogs() {
return Observable.of([ 's1', 's2', ... ]);
}
}
A component uses this service to display a list asynchronously when clicking a button:
#Component({
selector: 'my-list',
providers: [MyService],
template: `
<ul><li *ngFor="#item of items">{{ item }}</li></ul>
<div id="test" (click)="test()">Test</div>
`
})
export class MyList implements OnInit {
items:Array<string>;
service:MyService;
constructor(private service:MyService) {
}
test() {
this.service.getDogs().subscribe(
(dogs) => {
this.items = dogs;
});
}
}
I want to test that when I click on the "Test" button, the test method of the component is called and the getDogs method of the service is indirectly called.
For this, I create a test that instantiate directly the service and load the component using TestComponentBuilder. In this case, I need to call the overrideProviders method on it before calling createAsync. This way, you will be able to provide your spied service to be notified of the call. Here is a sample:
let service:MyService = new MyService();
beforeEach(() => {
spyOn(service, 'getDogs').and.returnValue(Observable.of(
['dog1', 'dog2', 'dog3']));
});
it('should test get dogs', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders(MyList, [provide(MyService, { useValue: service })])
.createAsync(MyList).then((componentFixture: ComponentFixture) => {
const element = componentFixture.nativeElement;
componentFixture.detectChanges();
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
}));
Edit
Since the event is triggered asynchronously, you could consider to use fakeAsync. The latter allows you to completly control when asynchronous processing are handled and turn asynchronous things in to synchronous ones.
You could wrap your test processing into
fakeAsync((): void => {
var clickButton = document.getElementById('test');
clickButton.dispatchEvent(new Event("click"));
expect(service.getDogs).toHaveBeenCalled();
});
For more details, you could have a look at this question:
Does fakeAsync guarantee promise completion after tick/flushMicroservice