Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): Add session handling implementation #5173

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ For semantic convention package changes, see the [semconv CHANGELOG](packages/se

### :rocket: (Enhancement)

* feat(web): add session handling implementation [5173](https://github.com/open-telemetry/opentelemetry-js/pull/5173)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* feat(web): add session handling implementation [5173](https://github.com/open-telemetry/opentelemetry-js/pull/5173)
* feat(web): add session handling implementation [#5173](https://github.com/open-telemetry/opentelemetry-js/pull/5173) @martinkuba


### :bug: (Bug Fix)

### :books: (Refine Doc)
Expand Down
19 changes: 19 additions & 0 deletions examples/opentelemetry-web/examples/session/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>Session Example</title>
<base href="/">

<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
Example of using attaching session attributes to spans and logs.
<script type="text/javascript" src="session.js"></script>
<button id="button1">generate span</button>
<button id="button2">generate log</button>
</body>

</html>
69 changes: 69 additions & 0 deletions examples/opentelemetry-web/examples/session/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const { ConsoleSpanExporter, SimpleSpanProcessor } = require( '@opentelemetry/sdk-trace-base');
const { WebTracerProvider } = require( '@opentelemetry/sdk-trace-web');
const {
LoggerProvider,
SimpleLogRecordProcessor,
ConsoleLogRecordExporter,
} = require('@opentelemetry/sdk-logs');
const {
createSessionSpanProcessor,
createSessionLogRecordProcessor,
Session,
SessionManager,
DefaultIdGenerator,
LocalStorageSessionStore
} = require('@opentelemetry/web-common');

// session manager
const sessionManager = new SessionManager({
sessionIdGenerator: new DefaultIdGenerator(),
sessionStore: new LocalStorageSessionStore(),
maxDuration: 20,
inactivityTimeout: 10
});

sessionManager.addObserver({
onSessionStarted: (newSession, previousSession) => {
console.log('Session started', newSession, previousSession);
},
onSessionEnded: (session) => {
console.log('Session ended', session);
}
});

// configure tracer
const provider = new WebTracerProvider();
provider.addSpanProcessor(createSessionSpanProcessor(sessionManager));
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();
const tracer = provider.getTracer('example');

// configure logger
const loggerProvider = new LoggerProvider();
loggerProvider.addLogRecordProcessor(createSessionLogRecordProcessor(sessionManager));
loggerProvider.addLogRecordProcessor(
new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())
);
const logger = loggerProvider.getLogger('example');

window.addEventListener('load', () => {
document.getElementById('button1').addEventListener('click', generateSpan);
document.getElementById('button2').addEventListener('click', generateLog);
});

function generateSpan() {
const span = tracer.startSpan('foo');
span.setAttribute('key', 'value');
span.end();
}

function generateLog() {
logger.emit({
attributes: {
name: 'my-event',
},
body: {
key1: 'val1'
}
});
}
1 change: 1 addition & 0 deletions examples/opentelemetry-web/webpack.dev.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const common = {
fetchXhrB3: 'examples/fetchXhrB3/index.js',
'fetch-proto': 'examples/fetch-proto/index.js',
zipkin: 'examples/zipkin/index.js',
session: 'examples/session/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
Expand Down
1 change: 1 addition & 0 deletions examples/opentelemetry-web/webpack.prod.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const common = {
fetchXhrB3: 'examples/fetchXhrB3/index.js',
"fetch-proto": "examples/fetch-proto/index.js",
zipkin: 'examples/zipkin/index.js',
session: 'examples/session/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
Expand Down
78 changes: 78 additions & 0 deletions experimental/packages/web-common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,84 @@ This package contains classes and utils that are common for web use cases.
npm install --save @opentelemetry/web-common
```

## Sessions

Sessions correlate multiple traces, events and logs that happen within a given time period. Sessions are represented as span/log attributes prefixed with the `session.` namespace. For additional information, see [documentation in semantic conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/session.md).

We provide a default implementation of managing sessions that:

- abstracts persisting sessions across page loads, with default implementation based on LocalStorage
- abstracts generating session IDs
- provides a mechanism for resetting the active session after maximum defined duration
- provides a mechanism for resetting the active session after a defined inactivity duration

Example:

```js
const {
createSessionSpanProcessor,
createSessionLogRecordProcessor,
SessionManager,
DefaultIdGenerator,
LocalStorageSessionStore
} = require('@opentelemetry/web-common');

// session manager
const sessionManager = new SessionManager({
sessionIdGenerator: new DefaultIdGenerator(),
sessionStore: new LocalStorageSessionStore(),
maxDuration: 7200, // 4 hours
inactivityTimeout: 1800 // 30 minutes
});

// configure tracer
const provider = new WebTracerProvider();
provider.addSpanProcessor(createSessionSpanProcessor(sessionManager));

// configure logger
const loggerProvider = new LoggerProvider();
loggerProvider.addLogRecordProcessor(createSessionLogRecordProcessor(sessionManager));
```

The above implementation can be customized by providing different implementations of SessionStore and SessionIdGenerator.

### Observing sessions

The SessionManager class provides a mechanism for observing sessions. This is useful when other components should be notified when a session is started or ended.

```js
sessionManager.addObserver({
onSessionStarted: (newSession, previousSession) => {
console.log('Session started', newSession, previousSession);
},
onSessionEnded: (session) => {
console.log('Session ended', session);
}
});
```

### Custom implementation of managing sessions

If you require a completely custom solution for managing sessions, you can still use the processors that attach attributes to spans/logs. Here is an example:

```js
function getSessionId() {
return 'abcd1234';
}

// configure tracer
const provider = new WebTracerProvider();
provider.addSpanProcessor(createSessionSpanProcessor({
getSessionId: getSessionId
}));

// configure logger
const loggerProvider = new LoggerProvider();
loggerProvider.addLogRecordProcessor(createSessionLogRecordProcessor({
getSessionId: getSessionId
}));
```

## Useful links

- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
Expand Down
38 changes: 38 additions & 0 deletions experimental/packages/web-common/src/DefaultIdGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { SessionIdGenerator } from './types/SessionIdGenerator';

export class DefaultIdGenerator implements SessionIdGenerator {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question here - if users can use composition instead, I'd advocate against exporting the class to make future changes easier. 🙂

generateSessionId = getIdGenerator(16);
}

const SHARED_CHAR_CODES_ARRAY = Array(32);
function getIdGenerator(bytes: number): () => string {
return function generateId() {
for (let i = 0; i < bytes * 2; i++) {
SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48;
// valid hex characters in the range 48-57 and 97-102
if (SHARED_CHAR_CODES_ARRAY[i] >= 58) {
SHARED_CHAR_CODES_ARRAY[i] += 39;
}
}
return String.fromCharCode.apply(
null,
SHARED_CHAR_CODES_ARRAY.slice(0, bytes * 2)
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Session } from './types/Session';
import { SessionStore } from './types/SessionStore';

const SESSION_STORAGE_KEY = 'opentelemetry-session';

export class LocalStorageSessionStore implements SessionStore {
save(session: Session): void {
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
}

get(): Session | null {
const sessionData = localStorage.getItem(SESSION_STORAGE_KEY);
if (sessionData) {
return JSON.parse(sessionData) as Session;
}
return null;
}
}
Loading