Skip to content

Commit 836def0

Browse files
adding Readme.md content + polishing solution
reducing slowness
1 parent c739960 commit 836def0

File tree

5 files changed

+215
-19
lines changed

5 files changed

+215
-19
lines changed

plugins/node/instrumentation-react-native-redux/README.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,198 @@
33
[![NPM Published Version][npm-img]][npm-url]
44
[![Apache License][license-image]][license-image]
55

6+
## Installation
7+
8+
```bash
9+
npm i @opentelemetry/instrumentation-react-native-redux @opentelemetry/api
10+
```
11+
12+
or if you use yarn
13+
14+
```bash
15+
yarn add @opentelemetry/instrumentation-react-native-redux @opentelemetry/api
16+
```
17+
18+
## Usage
19+
20+
`@opentelemetry/instrumentation-react-native-redux` exports a custom middleware that creates telemetry data for each action dispatched by your React Native application.
21+
22+
```javascript
23+
import {DarkTheme, DefaultTheme, ThemeProvider} from "@react-navigation/native";
24+
import {Stack} from "expo-router";
25+
import {Provider} from "react-redux";
26+
import store from './store';
27+
28+
export default function RootLayout() {
29+
30+
return (
31+
<Provider store={store}> {/* As with any regular Redux configuration, the provider should wrap the entire application at the root of the tree. There is nothing new or custom here. */}
32+
<Stack>
33+
<Stack.Screen
34+
name="(tabs)"
35+
options={{
36+
headerShown: false,
37+
}}
38+
/>
39+
<Stack.Screen name="+not-found" />
40+
</Stack>
41+
</Provider>
42+
);
43+
}
44+
```
45+
46+
Now, let's take a look at the Store configuration in `store.ts`. We need to implement the dispatch middleware:
47+
48+
```javascript
49+
// store.ts
50+
51+
import {dispatchMiddleware} from "@opentelemetry/instrumentation-react-native-redux";
52+
import {
53+
BasicTracerProvider,
54+
ConsoleSpanExporter,
55+
SimpleSpanProcessor,
56+
} from "@opentelemetry/sdk-trace-base";
57+
import {
58+
legacy_createStore as createStore,
59+
combineReducers,
60+
applyMiddleware,
61+
Reducer,
62+
} from "redux";
63+
import rootReducer from './reducer'
64+
65+
const exporter = new ConsoleSpanExporter();
66+
const processor = new SimpleSpanProcessor(exporter);
67+
const provider = new BasicTracerProvider();
68+
69+
provider.addSpanProcessor(processor);
70+
provider.register();
71+
72+
const middleware = dispatchMiddleware<RootState>(provider, {
73+
// Here the new middleware is configured in debug mode. It will display console messages in this case.
74+
// To shut them down, we need to turn it to `false`.
75+
debug: true,
76+
});
77+
78+
const store = createStore(rootReducer, applyMiddleware(middleware));
79+
export default store;
80+
```
81+
82+
Just for completeness of the example, the rest of the configuration can look like:
83+
84+
```javascript
85+
// actions.ts
86+
87+
type IncrementAction = {
88+
type: "COUNTER_INCREASE";
89+
count: number;
90+
};
91+
92+
type DecrementAction = {
93+
type: "COUNTER_DECREASE";
94+
count: number;
95+
};
96+
97+
type CounterState = {
98+
count: number;
99+
};
100+
101+
const initialCounterState: CounterState = {
102+
count: 0,
103+
};
104+
105+
const counterIncrease = (count = 1): IncrementAction => ({
106+
type: "COUNTER_INCREASE",
107+
count,
108+
});
109+
110+
const counterDecrease = (count = 1): DecrementAction => ({
111+
type: "COUNTER_DECREASE",
112+
count,
113+
});
114+
115+
/**
116+
* Counter actions (for testing purposes)
117+
*/
118+
type CounterActions = IncrementAction | DecrementAction;
119+
120+
export {counterIncrease, counterDecrease, initialCounterState};
121+
export type {CounterState, CounterActions}
122+
```
123+
124+
```javascript
125+
// reducers.ts
126+
127+
import { combineReducers, Reducer } from "redux";
128+
import { initialCounterState, CounterState, CounterActions } from './actions';
129+
130+
/**
131+
* Counter reducer (for testing purposes)
132+
*/
133+
const counterReducer: Reducer<CounterState, CounterActions> = (
134+
state = initialCounterState,
135+
action,
136+
) => {
137+
switch (action.type) {
138+
case "COUNTER_INCREASE": {
139+
return {...state, count: state.count + action.count};
140+
}
141+
case "COUNTER_DECREASE": {
142+
return {...state, count: state.count - action.count};
143+
}
144+
default:
145+
return state;
146+
}
147+
};
148+
149+
/**
150+
* Root reducer (in case of multiple reducers)
151+
*/
152+
const rootReducer = combineReducers({
153+
counter: counterReducer,
154+
});
155+
156+
type RootState = ReturnType<typeof rootReducer>;
157+
158+
export default rootReducer;
159+
export type {RootState};
160+
```
161+
162+
This middleware was prepared to accept a custom provider to start creating telemetry data, but if it is not passed, it will use a global tracer provider instead.
163+
Internally, each `redux` dispatched action can take a few milliseconds to process (depending on how heavy the transaction is) and that is why this instrumentation library produces Spans. Each Span will display a static name, and besides that, it will add a few attributes.
164+
165+
The following object is the representation of what the middleware will create for a dispatched action:
166+
167+
```bash
168+
{
169+
resource: {
170+
attributes: {
171+
'service.name': 'unknown_service:node',
172+
'telemetry.sdk.language': 'nodejs',
173+
'telemetry.sdk.name': 'opentelemetry',
174+
'telemetry.sdk.version': '1.25.1'
175+
}
176+
},
177+
traceId: '9c591b898c2e85c49f283d1e6393aae1',
178+
parentId: undefined,
179+
traceState: undefined,
180+
name: 'action',
181+
id: 'aa80b027f2570181',
182+
kind: 0,
183+
timestamp: 1722889197399000,
184+
duration: 52.166,
185+
attributes: {
186+
'action.payload': '{"count":1}',
187+
'action.type': 'COUNTER_DECREASE',
188+
'action.state': 'active'
189+
},
190+
status: { code: 0 },
191+
events: [],
192+
links: []
193+
}
194+
```
195+
As mentioned above, the `name` of this Span is `action` (static). The `action.type` is injected as an attribute, as well as `action.payload`, which is a string that adds whatever the action has (except for the mentioned `action.type`).
196+
Finally, the middleware adds the `action.state`, which provides information about the state of the application (`AppState.currentState`).
197+
6198
## Useful links
7199
8200
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>

plugins/node/instrumentation-react-native-redux/src/dispatch.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,27 @@
1414
* limitations under the License.
1515
*/
1616
import { Dispatch, Middleware, Action, UnknownAction } from 'redux';
17-
import { TracerProvider, trace } from '@opentelemetry/api';
17+
import { TracerProvider, trace, Attributes } from '@opentelemetry/api';
1818
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
19-
import { spanEnd, spanStart } from './utils/spanFactory';
19+
import {
20+
ATTRIBUTES,
21+
spanEnd,
22+
spanStart,
23+
STATIC_NAME,
24+
} from './utils/spanFactory';
2025
import logFactory from './utils/logFactory';
2126

22-
const SPAN_NAME = {
23-
thunk: 'thunk',
24-
action: 'action',
25-
};
26-
2727
interface MiddlewareConfig {
2828
debug?: boolean;
29+
name?: string; // custom name for each action
30+
attributes?: Attributes;
2931
}
3032

3133
const middleware = <RootState>(
3234
provider: TracerProvider | undefined,
3335
config?: MiddlewareConfig
3436
): Middleware<object, RootState> => {
35-
const { debug } = config || {};
37+
const { debug, name, attributes } = config || {};
3638
const console = logFactory(!!debug);
3739

3840
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -50,15 +52,15 @@ const middleware = <RootState>(
5052

5153
return (next: Dispatch<UnknownAction>) => {
5254
return (action: Action) => {
53-
const span = spanStart(tracer, SPAN_NAME.action);
55+
const span = spanStart(tracer, name ?? STATIC_NAME, { attributes });
5456
const result = next(action);
5557

5658
if (span) {
5759
const { type, ...otherValues } = result;
5860

5961
span.setAttributes({
60-
type,
61-
payload: JSON.stringify(otherValues),
62+
[ATTRIBUTES.type]: type,
63+
[ATTRIBUTES.payload]: JSON.stringify(otherValues),
6264
});
6365

6466
spanEnd(span);

plugins/node/instrumentation-react-native-redux/src/utils/spanFactory.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
import { AppState } from 'react-native';
1717
import { Context, Span, SpanOptions, Tracer } from '@opentelemetry/api';
1818

19+
const STATIC_NAME = 'action';
1920
const ATTRIBUTES = {
20-
name: 'action.name',
21-
appState: 'action.state',
21+
payload: `${STATIC_NAME}.payload`,
22+
type: `${STATIC_NAME}.type`,
23+
appState: `${STATIC_NAME}.state`,
2224
};
2325

2426
const spanStart = (
@@ -42,4 +44,4 @@ const spanEnd = (span: Span | null) => {
4244
}
4345
};
4446

45-
export { spanStart, spanEnd, ATTRIBUTES };
47+
export { spanStart, spanEnd, ATTRIBUTES, STATIC_NAME };

plugins/node/instrumentation-react-native-redux/test/dispatch.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ describe('dispatch.ts', () => {
8787
timestamp: sandbox.match.number,
8888
duration: sandbox.match.number,
8989
attributes: {
90-
type: 'COUNTER_INCREASE:slow',
90+
'action.type': 'COUNTER_INCREASE:slow',
9191
'action.state': 'background',
92-
payload: '{"count":3}',
92+
'action.payload': '{"count":3}',
9393
},
9494
}),
9595
sandbox.match({
@@ -106,9 +106,9 @@ describe('dispatch.ts', () => {
106106
timestamp: sandbox.match.number,
107107
duration: sandbox.match.number,
108108
attributes: {
109-
type: 'COUNTER_DECREASE:normal',
109+
'action.type': 'COUNTER_DECREASE:normal',
110110
'action.state': 'background',
111-
payload: '{"count":1}',
111+
'action.payload': '{"count":1}',
112112
},
113113
}),
114114
sandbox.match({

plugins/node/instrumentation-react-native-redux/test/helper/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const counterReducer: Reducer<CounterState, CounterActions> = (
6666
switch (action.type) {
6767
case 'COUNTER_INCREASE:slow': {
6868
// NOTE: adding slowliness to the action for testing purposes
69-
const test = new Array(100000000);
69+
const test = new Array(1000000);
7070
test.forEach((_, index) => (test[index] = true));
7171

7272
return { ...state, count: state.count + action.count };

0 commit comments

Comments
 (0)