How to get Electron + rxdb to work? - javascript

I want to learn and develop a desktop app by using electron + rxdb.
My file structure:
main.js (the main process of electron)
/js-server/db.js (all about rxdb database, include creation)
/js-client/ui.js (renderer process of electron)
index.html (html home page)
main.js code:
const electron = require('electron')
const dbjs = require('./js-server/db.js')
const {ipcMain} = require('electron')
ipcMain.on('search-person', (event, userInput) => {
event.returnValue = dbjs.searchPerson(userInput);
})
db.js code:
var rxdb = require('rxdb');
var rxjs = require('rxjs');
rxdb.plugin(require('pouchdb-adapter-idb'));
const personSchema = {
title: 'person schema',
description: 'describes a single person',
version: 0,
type: 'object',
properties: {
Name: {type: 'string',primary: true},
Age: {type: 'string'},
},
required: ['Age']
};
var pdb;
rxdb.create({
name: 'persondb',
password: '123456789',
adapter: 'idb',
multiInstance: false
}).then(function(db) {
pdb = db;
return pdb.collection({name: 'persons', schema: personSchema})
});
function searchPerson(userInput) {
pdb.persons.findOne().where('Name').eq(userInput)
.exec().then(function(doc){return doc.Age});
}
module.exports = {
searchPerson: searchPerson
}
ui.js code:
const {ipcRenderer} = require('electron');
function getFormValue() {
let userInput = document.getElementById('searchbox').value;
displayResults(ipcRenderer.sendSync("search-person",userInput));
document.getElementById('searchbox').value = "";
}
Whenever I run this app, I got these errors:
(node:6084) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: RxError:
RxDatabase.create(): Adapter not added. (I am sure I've installed the pouched-adapter-idb module successfully)
Type error, cannot read property "persons" of undefined. (this error pops out when I search and hit enter to the form in index.html)
I am new to programming, especially js, I've been stuck on these errors for a week, just can't get it to work. Any help? Thanks.

The problem is that this line is in main.js:
const dbjs = require('./js-server/db.js')
Why? Because you're requiring RxDB inside the main process and using the IndexedDB adapter. IndexedDB is a browser API and thus can only be used in a rendering process. In Electron, the main process is a pure Node/Electron environment with no access to the Chromium API's.
Option #1
If you want to keep your database in a separate thread then consider spawning a new hidden browser window:
import {BrowserWindow} from 'electron'
const dbWindow = new BrowserWindow({..., show: false})
And then use IPC to communicate between the two windows similarly to how you have already done.
Option #2
Use a levelDB adapter that only requires NodeJS API's so you can keep your database in the main process.

Related

Yeoman generator add a new file generated exsiting project

I've yeoman generator which generate a simple sproject successfully.
I want that after the project generation, in latter time that the use will have the ability to generate a new file deployment.yaml under the app folder, however it needs to read some data from the main generator
for example appName as the sub-generator needs to generate a new file
inside the generated application.
e.g. yo tdk
This command generates a new project
And when I run yo tdk:event (or something similar) it will generate a new file inside the project app folder
For illustration I've created this very simple generator
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
prompting() {
this.props = {
appName: "my-app",
srvName: "my-service"
};
const prompts = [
{
name: "appName",
message: "Project name: ",
type: "input",
default: this.props.appName
},
{
name: "srvName",
message: "Service name: ",
type: "input",
default: this.props.srvName
}
];
return this.prompt(prompts).then(props => {
this.props = props;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("app"),
this.destinationPath(this.props.appName),
this.props
);
}
};
This generator have two simple question
app name
service name
And it will generate a project like
myapp /root
-app /folder
- service.yaml /single file at the project generation
The generated service.yaml looks like following:
apiVersion: v1
kind: Service
metadata:
name: <%= appName %>
spec:
selector:
app: <%= srvName %>
ports:
- protocol: TCP
port: 80
Now after the generation of the project with this service.yaml file
I want in latter time (after the project generation)to add new file deployment.yaml under the app folder
deployment.yaml
apiVersion: v1
kind: Deployment
metadata:
name: <%= appName %> //this is the appname from the project generation
spec:
replicas: <%= replica %>
selector:
app: <%= srvName %>
The appName & srvName are coming from the main generator,
(I saw that there is option to share data between sub generator
https://yeoman.io/authoring/storage.html , not sure how to share this between generators )
and the replica should come from the new/sub generator
This is the project structure after the generation
myapp /root
-app /folder
- service.yaml /single file at the project generation
- deployment.yaml / new file added to the project under app folder
Like user start another generator/sub and have a new question e.g. how much replicas do you want? and then generates the file.
How can I do it ?
update
This is my project strucutre
myapp
- node_modules
- package.json //here I declare the main-generator command -> tdk
- generators
-- app
---index.ts
--deployment
---index.ts
---package.json //here I declare the sub-generator command -> deploy
- node_modules
- package.json
-.yo-rc.json //here I see the data that I keep via config.set api
Update
When I call to the sub generator via program like
const yeoman = require('yeoman-environment');
const env = yeoman.createEnv();
env.lookup(function () {
env.run("tdk:deploy", {
replicas: 100
}, (err) => {
console.log("done", err);
});
});
I got error:
out from config undefined : undefined //the undefind is from the console in the sub-generator
done TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined
at validateString (internal/validators.js:125:11)
at Object.join (path.js:1037:7)
I put a console.log in the subgenerator code like
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
console.log("out from config", this.srvName, ":", this.appName);
}
And when I run the subgenerator I got empty config ( from the .yo-rc.json)
while checking the .yo-rc.json . I was able to see the entry from the main generator, the data was stored but when I run it from the program it doesnt find it...any idea ?
This is the link for both project (very basic yeoman generator which demonstrate the point) just need to run npm install for both projects
and for the generator run also npm link.
At the end: a project should be generated with two files
1. service.yaml // generated from the main generator
2. deployment.yaml - // generated from sub generator with the properties from the main & sub generator
currently, the deployment.yaml file is not generated
https://drive.google.com/drive/folders/1kBnZxpVcRR9qhGZagVtod7W4wFmt73C6
1 . generator-tdk - Generator and sub-generator
2. yeomanEnv - The code which is running the sub-generator to create the file inside the generated project
What am I doing wrong ? :(
if there is a way from the sub-generator to read the .yo-rc.json , it can help
You can set the values to config inside configuring of the main generator like this:
configuring() {
this.config.set('appName', this.props.appName);
this.config.set('srvName', this.props.srvName);
}
and read the values inside the sub-generators:
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
So you'll have access to these values via this.srvName and this.appName upon writing.
Example code:
app/index.js:
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
prompting() {
this.props = {
appName: "my-app",
srvName: "my-service",
};
const prompts = [
{
name: "appName",
message: "Project name: ",
type: "input",
default: this.props.appName,
},
{
name: "srvName",
message: "Service name: ",
type: "input",
default: this.props.srvName,
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
});
}
configuring() {
this.config.set('appName', this.props.appName);
this.config.set('srvName', this.props.srvName);
}
writing() {
this.fs.copyTpl(
this.templatePath("app"),
this.destinationPath(this.props.appName),
this.props
);
}
};
deploy/index.js:
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
prompting() {
this.props = {
replicas: 0,
};
const prompts = [
{
name: "replica",
message: "how much replicas do you want?",
type: "input",
default: this.props.replicas,
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("deploy"),
this.destinationPath(this.appName),
{
srvName: this.srvName,
appName: this.appName,
...this.props,
}
);
}
};
and commands:
yo <name for the main project generation
yo <name>:deploy to ask for replicas and create deployment.yaml
To execute the sub-generator without the use of yo:
var yeoman = require("yeoman-environment");
var env = yeoman.createEnv();
env.lookup(function () {
env.run("<name>:deploy", {
replicas: 100
}, (err) => {
console.log("done", err);
});
});
and a sample sub-generator that skips question if values are passed via options (deploy/index.js):
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
prompting() {
this.props = {
replicas: 0,
};
const prompts = [
{
name: "replicas",
message: "which app to generate?",
type: "input",
default: this.props.replicas,
when: !this.options.replicas, // disable the question if it's found in options
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
// set values from options (if found)
this.props.replicas = this.options.replicas || this.props.replicas;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("deploy"),
this.destinationPath(this.appName),
{
srvName: this.srvName,
appName: this.appName,
...this.props,
}
);
}
};

Interpolate env vars client side in gatsby react app

I am using Gatsby as a Static Site Generator and using Netlify to deploy.
Netlify lets you set Environment Variables in its UI backend.
I've set a few env vars in the Netlify backend to be able to post subscribers to a mailing list.
DATA_NO = 'XXXX'
LIST_ID = '123456'
API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXX'
In my src files, I've got a component that responds to a onSubmit event and constructs a URL to post a new subscriber.
(axios is used as a package for sending HTTP requests, etc)
import React, { useState } from "react"
import axios from 'axios'
const Form = () => {
const [userEmail, setState] = useState({'email_address': ''})
const creds = 'anystring:'+ process.env.API_KEY
let URL = 'https://'+ process.env.DATA_NO +'.api.example.com/3.0'
URL += '/lists/'+ process.env.LIST_ID +'/members'
const submitSubscribe = async e => {
e.preventDefault()
const payload = {
'email_address': userEmail.email_address,
'status': 'subscribed'
}
try {
const response = await axios.post( URL , payload, {
headers: {
'Authorization': 'Basic ' + Buffer.from(creds ).toString('base64')
}
})
console.log('r', response)
console.log('r data', response.data)
} catch(err) {
console.log(err);
}
}
return (
<form name="newsletter-signup" method="post" onSubmit={submitSubscribe}>
{/*<input type="hidden" name="form-name" value="newsletter-signup" />*/}
<input type="email" placeholder="Email required" onChange={handleChange} value={userEmail.email_address} required />
<button type="submit" className="button primary-button-inverted">Send'</button>
</form>
)
}
So, what's happening is that on RUN time, my env vars are coming out as undefined.
I've been on the Netlify docs and they keep saying you need to interpolate the values to the client to be able to use them. I understand the logic here. These env vars need to be printed and bundled during build time, not invoked at run time.
The question I'm struggling with is HOW do I do this?
I have set up a .env.development file in the root of my project. I have tried prefixing my env vars with GATSBY_ but I still have the same trouble.
I tried using require('dotenv').config() but I'm not sure where exactly to put that (in my gatsby-node.js, gatsby-config.js) or do I need to include on the page with my component that is using these env vars.
I'd like to be able to set these vars up in one place (maybe two if testing in development) but I don't want to much tweaking involved to be able to use them in both development and production builds.
I also understand that Netlify or Gatsby can process these vars into a functions/ folder in my source code that I can somehow make use of but that seems like more than I need to just post a simple form.
Please help!
Update
Current code:
In my project root, I created two .env files, one for development and one for production. They each share the following format (remember, I am developing in GatsbyJS):
GATSBY_MC_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxx-xxxx"
GATSBY_MC_DATA_NO="xxxx"
GATSBY_MC_AUDIENCE_ID="xxxxxxxxxxx"
I've set up a separate config.js file in src/config/config.js to organize and validate my env vars (thanks #Baboo_). It looks like:
export const MC_API_KEY = process.env.GATSBY_MC_API_KEY;
export const MC_DATA_NO = process.env.GATSBY_MC_DATA_NO;
export const MC_AUDIENCE_ID = process.env.GATSBY_MC_AUDIENCE_ID;
const envVars = [
{name: "MC_API_KEY", value: MC_API_KEY},
{name: "MC_DATA_NO", value: MC_DATA_NO},
{name: "MC_AUDIENCE_ID", value: MC_AUDIENCE_ID}
]
export const checkEnvVars = () => {
const envVarsNotLoaded = envVars.filter((envVar) => envVar.value !== undefined);
if (envVarsNotLoaded.length > 0) {
throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
}
}
checkEnvVars()
However, when I run gatsby develop, the "Could not load env vars" error gets thrown.
You are doing it the right way.
What you have to do is indeed prefix your environment variables with GATSBY_, Gatsby will automatically load them. Then call them in your code:
const creds = 'anystring:'+ process.env.GATSBY_API_KEY
let URL = 'https://'+ process.env.GATSBY_DATA_NO +'.api.example.com/3.0'
tURL += '/lists/'+ process.env.GATSBY_LIST_ID +'/members'
Make sure to use the whole string process.env.GATSBY_LIST_ID instead of process.env[GATSBY_LIST_ID] because the object process.env is undefined.
Locally
Make sure to create to .env files, .env.development and .env.production. The former is used when you run gatsby develop and the latter when you run gatsby build.
You may already know that you shouldn't commit these files.
Netlify
Add the same environment variables in your deployment pipeline on Netlify. Here is the related doc. This way Netlify can build your webiste when being deployed.
Improvements
Instead of refering environment variables directly, create a file where they are loaded and if one of them cannot be retrieved, throw an error. This way you will be noticed when the loading fails and save debugging time.
Example:
// config.js
export const API_KEY = process.env.GATSBY_API_KEY;
export const DATA_NO = process.env.GATSBY_DATA_NO ;
const envVars = [
{name: "API_KEY", value: API_KEY},
{name: "DATA_NO", value: DATA_NO},
]
const checkEnvVars = () => {
const envVarsNotLoaded = envVars.filter(isUndefined);
if (envVarsNotLoaded.length > 0) {
throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
}
}
const isUndefined = (envVar) => typeof envVar.value === "undefined";
// component.js
import React, { useState } from "react"
import axios from 'axios'
// Import environment variables
import { API_KEY, DATA_NO } from "./config"
const Form = () => {
// ...
const [userEmail, setState] = useState({'email_address': ''})
const creds = 'anystring:'+ API_KEY
let URL = 'https://'+ DATA_NO +'.api.example.com/3.0'
You need to add a different env file for the two environments to make this work.
Meaning .env.development and .env.production.

Creating a service worker in vue.js using workbox's injectmanifest crashes

I am trying to use vue.js's progressive web app capabilities by creating a custom service worker through workbox. Everytime I attempt to build the app I get the following error:
AssertionError [ERR_ASSERTION]: swSrc must be set to the path to an existing service worker file.
project/vue.config.js:
module.exports = {
runtimeCompiler: true,
pwa: {
workboxPluginMode: "InjectManifest",
plugins: [
new InjectManifest({
swSrc: "src/service-worker.js"
})
]
}
};
project/src/service-worker.js:
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
//Web Push Notifications//
let click_open_url;
self.addEventListener("push", function(event) {
let push_message = event.data.json();
// push notification can send event.data.json() as well
click_open_url = push_message.notification.data.url;
const options = {
body: push_message.notification.body,
icon: push_message.notification.icon,
image: push_message.notification.image,
tag: "alert"
};
event.waitUntil(
self.registration.showNotification(push_message.notification.title, options)
);
});
self.addEventListener("notificationclick", function(event) {
const clickedNotification = event.notification;
clickedNotification.close();
if (click_open_url) {
const promiseChain = clients.openWindow(click_open_url);
event.waitUntil(promiseChain);
}
});
I have tried changing the formatting on swSrc to lead with ./ or just / and even removing src/ but none of these have done anything. I have also tried coping code generated by workbox, then pasting it into service-worker.js, but it still does not recognize it. How do I get InjectManifest to recognize my my service worker file?
I answered my own question. I needed to change project/vue.config.js to
module.exports = {
runtimeCompiler: true,
pwa: {
workboxPluginMode: "InjectManifest",
workboxOptions:{
swSrc: "src/service-worker.js"
}
};

Why isn't this sample Electron code working: Loading a markdown file into an editor?

My setup
Win10
Electron 2.0.1
I'm following an example from the book Electron in Action , Chapter 3, where the sample app loads a Markdown file into an Electron editor and shows it in HTML within a dual-pane view.
However, having followed the exact procedure and arrived at the exact same source code at the end of the chapter. I'm seeing nothing the author was trying to show.
Repro:
Run the project;
Open a .md file;
Notice that the two panes are blank.
I'm pretty new to this and fail to see anything useful from the console. So if anyone could code-review the following short listings it'd be much appreciated!
Here is the main.js
const { app, BrowserWindow, dialog } = require('electron');
const fs = require('fs');
let mainWindow = null;
app.on('ready', () => {
console.log('Hello from Electron');
mainWindow = new BrowserWindow({
show: false, // Delay showing window to avoid initial blank.
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadFile('./index.html'); // relpath = same folder as main.js
mainWindow.once('ready-to-show', () => {
mainWindow.show();
})
mainWindow.on('closed', () => {
mainWindow = null;
});
});
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(files[0]); }
};
const openFile = (file) => {
const content = fs.readFileSync(file).toString();
mainWindow.webContents.send('file-opened', file, content);
}
Here is renderer.js
const { remote, ipcRenderer } = require('electron');
const mainProcess = remote.require('./main.js'); // plug in main process
const marked = require('marked'); // import marked as marked
// From document(index.html), find and refer to section '#markdown'
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
});
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser();
});
ipcRenderer.on('file-opened', (event, file, content) => {
markdownView.value = content;
renderMarkdownToHtml(content);
});
UPDATE
I opened the Developer Tool inside the app window and saw this error
remote.js:221 Uncaught Error: Could not call remote method 'getFileFromUser'. Check that the method signature is correct. Underlying error: The "path" argument must be one of type string, Buffer, or URL. Received type undefined
Underlying stack: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be one of type string, Buffer, or URL. Received type undefined
at Object.openSync (fs.js:439:10)
at Object.func (electron/js2c/asar.js:140:31)
at Object.func [as openSync] (electron/js2c/asar.js:140:31)
at Object.readFileSync (fs.js:349:35)
at Object.fs.readFileSync (electron/js2c/asar.js:542:40)
at Object.fs.readFileSync (electron/js2c/asar.js:542:40)
at openFile (path\to\hello_electron\app\main.js:36:24)
at Object.exports.getFileFromUser (path\to\hello_electron\app\main.js:32:18)
at electron/js2c/browser_init.js:6620:63
at EventEmitter.<anonymous> (electron/js2c/browser_init.js:6473:21)
at electron/js2c/browser_init.js:6622:17
at EventEmitter.<anonymous> (electron/js2c/browser_init.js:6473:21)
at EventEmitter.emit (events.js:203:13)
at WebContents.<anonymous> (electron/js2c/browser_init.js:3845:23)
at WebContents.emit (events.js:203:13)
path\to\hello_electron\node_modules\marked\lib\marked.js:1541 marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options
checkSanitizeDeprecation # path\to\hello_electron\node_modules\marked\lib\marked.js:1541
But looking at the main.js code above, I can't find why the path is invalid.
UPDATE 2
I found that the book sample was expecting a different returned value of showOpenDialog from my version.
My API doc says about showOpenDialog
/**
* Resolve with an object containing the following:
*
* * `canceled` Boolean - whether or not the dialog was canceled.
* * `filePaths` String[] - An array of file paths chosen by the user. If the
* dialog is cancelled this will be an empty array.
* * `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the
* `filePaths` array of base64 encoded strings which contains security scoped
* bookmark data. `securityScopedBookmarks` must be enabled for this to be
* populated.
*
* The `browserWindow` argument allows the dialog to attach itself to a parent
* window, making it modal.
*
* The `filters` specifies an array of file types that can be displayed or selected
* when you want to limit the user to a specific type. For example:
*
* The `extensions` array should contain extensions without wildcards or dots (e.g.
* `'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the
* `'*'` wildcard (no other wildcard is supported).
*
* **Note:** On Windows and Linux an open dialog can not be both a file selector
* and a directory selector, so if you set `properties` to `['openFile',
* 'openDirectory']` on these platforms, a directory selector will be shown.
*/
So I switched my code to use files.filePaths, but got new errors
remote.js:221 Uncaught Error: Could not call remote method 'getFileFromUser'. Check that the method signature is correct. Underlying error: Cannot read property '0' of undefined
Underlying stack: TypeError: Cannot read property '0' of undefined
at Object.exports.getFileFromUser (path\to\hello\app\main.js:37:29)
at electron/js2c/browser_init.js:6620:63
at EventEmitter.<anonymous> (electron/js2c/browser_init.js:6473:21)
at EventEmitter.emit (events.js:203:13)
at WebContents.<anonymous> (electron/js2c/browser_init.js:3845:23)
at WebContents.emit (events.js:203:13)
at electron/js2c/browser_init.js:6622:17
at EventEmitter.<anonymous> (electron/js2c/browser_init.js:6473:21)
at EventEmitter.emit (events.js:203:13)
at WebContents.<anonymous> (electron/js2c/browser_init.js:3845:23)
at WebContents.emit (events.js:203:13)
OK, solved it myself. It seems that the book was using the showOpenDialog API according to a wrong/outdated syntax. It is likely that the API used to be synchronous, but later became async by default. I had to replace the sample code
const getFileFromUser = exports.getFileFromUser = () => {
const files = dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(files[0]); }
};
with an async version
const getFileFromUser = exports.getFileFromUser = () => {
dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
}).then(result => {
if (result.filePaths.length > 0) { openFile(result.filePaths[0]); }
}).catch(err => {
console.log(err);
})
};
Also the returned promise contains more content than the book sample expects.
After this fix, I can see the loaded Markdown file in the panes as expected.

#sentry/node integration to wrap bunyan log calls as breadcrumbs

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);
}
}
})()
}

Categories