|
3 | 3 | [![NPM Published Version][npm-img]][npm-url] |
4 | 4 | [![Apache License][license-image]][license-image] |
5 | 5 |
|
| 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 | +
|
6 | 198 | ## Useful links |
7 | 199 |
|
8 | 200 | - For more information on OpenTelemetry, visit: <https://opentelemetry.io/> |
|
0 commit comments