HarmonyOS Application Integration¶
By collecting Metrics data from HarmonyOS applications, analyze application performance in a visualized manner.
Prerequisites¶
Note: If you have enabled the RUM Headless service, the prerequisites have been automatically configured for you, and you can directly integrate the application.
- Install DataKit;
- Configure the RUM Collector;
- Configure DataKit to be publicly accessible and install the IP Geolocation Library.
Application Integration¶
- Go to RUM > Create Application > HarmonyOS;
- Enter the application name and application ID;
- Select the application integration method:
- Public DataWay: Directly receives RUM data without installing the DataKit collector.
- Local Environment Deployment: Receives RUM data after meeting the prerequisites.
Installation¶
- Download the latest
ft_sdk.harfile: https://static.guance.com/ft-sdk-package/harmony_os_sdk_har/0.1.0-alpha01/ft_sdk.har
HAR Package Dependency Configuration:
According to the HarmonyOS official documentation (HAR Package Import Guide), the HAR file should be placed in the libs directory of the project.
Add the dependency in the project's oh-package.json5:
Note:
- Ensure the
ft_sdk.harfile is in thelibs/directory (create the directory if it does not exist) - If the HAR file is in the root directory, move it to the
libs/directory
Then run:
After installation, the HAR package will be installed in the oh_modules/ft_sdk/ directory.
Import Instructions:
According to the actual structure of the HAR package, use the following method to import:
import { FTSDK } from 'ft_sdk/src/main/ets/components/FTSDK';
import { FTSDKConfig, FTRUMConfig, FTLoggerConfig } from 'ft_sdk/src/main/ets/components/Configs';
Permission Instructions:
The SDK module automatically includes the following permission declarations, and users do not need to manually configure them. When the application integrates the SDK, these permissions will automatically take effect:
| Permission Name | Purpose Description |
|---|---|
ohos.permission.INTERNET |
Internet access permission, used for data reporting and network request tracking |
ohos.permission.GET_WIFI_INFO |
Get WiFi information, used for network type detection and signal strength collection |
ohos.permission.GET_NETWORK_INFO |
Get network information, used for network status monitoring and type identification |
Initialization¶
Initialize the SDK in EntryAbility.ets:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { FTSDK } from 'ft_sdk/src/main/ets/components/FTSDK';
import { FTSDKConfig, FTRUMConfig, FTLoggerConfig } from 'ft_sdk/src/main/ets/components/Configs';
import { TraceType } from 'ft_sdk/src/main/ets/components/trace/TraceType';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
await this.initFTSDK();
}
private async initFTSDK(): Promise<void> {
try {
// Local Environment Deployment (Datakit)
// const sdkConfig = FTSDKConfig.builder(datakitUrl);
// Public DataWay
const sdkConfig = FTSDKConfig.builder(datawayUrl, clientToken)
.setDebug(true)
.setServiceName('Your-App-Name');
// dbCacheLimit defaults to 100MB, call .setDbCacheLimit(sizeInBytes) to customize
// rumCacheLimitCount defaults to 100000, call .setRumCacheLimitCount(count) to customize
// logCacheLimitCount defaults to 5000, call .setLogCacheLimitCount(count) to customize
await FTSDK.install(sdkConfig, this.context);
const rumConfig = new FTRUMConfig()
.setSamplingRate(1.0)
.setRumAppId('your-app-id')
.setEnableTraceUserAction(true)
.setEnableTraceUserView(true)
.setEnableTraceUserResource(true)
.setEnableTrackAppANR(true);
await FTSDK.installRUMConfig(rumConfig);
const logConfig = new FTLoggerConfig()
.setSamplingRate(1.0)
.setEnableCustomLog(true)
.setLogCacheLimitCount(10000)
.setEnableLinkRumData(true);
FTSDK.installLogConfig(logConfig);
hilog.info(DOMAIN, 'FTSDK', 'FT SDK initialized successfully');
} catch (error) {
const errorObj: object = error as object;
hilog.error(DOMAIN, 'FTSDK', `Failed to initialize FT SDK: ${JSON.stringify(errorObj)}`);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'FTSDK', `loadContent failed: ${err.code}`);
return;
}
hilog.info(DOMAIN, 'FTSDK', 'Succeeded in loading the content.');
});
}
}
| Field | Type | Required | Description |
|---|---|---|---|
| datakitUrl | string | Yes | Local environment deployment (Datakit) reporting URL address, example: http://10.0.0.1:9529, default port 9529, the device installing the SDK must be able to access this address. Note: Choose one between datakitUrl and datawayUrl |
| datawayUrl | string | Yes | Public Dataway reporting URL address, obtained from the [RUM] application, example: https://open.dataway.url, the device installing the SDK must be able to access this address. Note: Choose one between datakitUrl and datawayUrl |
| clientToken | string | Yes | Authentication token, must be configured with datawayUrl |
| debug | boolean | No | Set whether to allow printing Debug logs, default false |
| env | string | No | Environment, default prod, any character, recommended to use a single word, such as test etc |
| serviceName | string | No | Set the name of the business or service, default: df_rum_harmonyos |
| autoSync | boolean | No | Whether to automatically synchronize data to the server after collection. Default true. When false, use the flushSyncData method to manage data synchronization |
| syncPageSize | number | No | Set the number of entries for synchronization requests. Range [5,), note: the larger the number of entries, the more computational resources the data synchronization will occupy, default is 10 |
| dbCacheLimit | number | No | DB cache limit size. Range [30MB,), default 100MB, unit byte |
| rumCacheLimitCount | number | No | RUM data cache count limit, default 100000, minimum 10000 |
| logCacheLimitCount | number | No | Log data cache count limit, default 5000, minimum 1000 |
| dbCacheDiscard | string | No | Set the data discard rule in the database. Discard policy: discard discard new data (default), discardOldest discard old data |
RUM Configuration¶
const rumConfig = new FTRUMConfig()
.setRumAppId('your-app-id')
.setSamplingRate(1.0)
.setEnableTraceUserAction(true)
.setEnableTraceUserView(true)
.setEnableTraceUserResource(true)
.setEnableTrackAppANR(true)
.setEnableTraceWebView(true); // Enable WebView data collection
await FTSDK.installRUMConfig(rumConfig);
| Method | Type | Required | Description |
|---|---|---|---|
| setRumAppId | string | Yes | RUM application ID, obtained from the [RUM] application |
| setSamplingRate | number | No | RUM sampling rate, range [0.0, 1.0], default 1.0 (100% collection) |
| setEnableTraceUserAction | boolean | No | Whether to enable automatic action tracking (UI click events), default false |
| setEnableTraceUserView | boolean | No | Whether to enable page tracking, default false |
| setEnableTraceUserResource | boolean | No | Whether to enable resource tracking (HTTP requests), default false |
| setEnableTrackAppANR | boolean | No | Whether to enable ANR monitoring, default false |
| setEnableTraceWebView | boolean | No | Whether to enable WebView data collection, default false |
| setRumCacheLimitCount | number | No | RUM data cache count limit, default 100000, minimum 10000 |
Log Configuration¶
import { Status } from 'ft_sdk/src/main/ets/components/bean/Status';
const logConfig = new FTLoggerConfig()
.setSamplingRate(1.0)
.setEnableCustomLog(true)
.setLogCacheLimitCount(10000)
.setLogLevelFiltersString([
Status.ERROR.name,
Status.WARNING.name,
Status.INFO.name,
Status.DEBUG.name
])
.setEnableLinkRumData(true);
FTSDK.installLogConfig(logConfig);
| Method | Type | Required | Description |
|---|---|---|---|
| setSamplingRate | number | No | Log sampling rate, range [0.0, 1.0], default 1.0 (100% collection) |
| setEnableCustomLog | boolean | No | Whether to enable custom logs, default false |
| setLogCacheLimitCount | number | No | Log data cache count limit, default 5000, minimum 1000 |
| setLogLevelFiltersString | string[] | No | Log level filtering, options: ERROR、WARNING、INFO、DEBUG |
| setEnableLinkRumData | boolean | No | Whether to associate RUM data, default false |
Trace Configuration¶
import { TraceType } from 'ft_sdk/src/main/ets/components/trace/TraceType';
const traceConfig = new FTTraceConfig()
.setSamplingRate(0.8)
.setTraceType(TraceType.DDTRACE)
.setEnableAutoTrace(true)
.setEnableLinkRUMData(true);
FTSDK.installTraceConfig(traceConfig);
| Method | Type | Required | Description |
|---|---|---|---|
| setSamplingRate | number | No | Set the collection rate, range [0,1], 0 means no collection, 1 means full collection, default value is 1 |
| setTraceType | TraceType | No | Set the type of trace, default is DDTRACE, currently supports ZIPKIN_MULTI_HEADER、ZIPKIN_SINGLE_HEADER、JAEGER、DDTRACE、SKYWALKING、TRACEPARENT (W3C), if integrating OpenTelemetry, please check the supported types and agent related configuration |
| setEnableLinkRUMData | boolean | No | Whether to associate with RUM data, default is false |
| setEnableAutoTrace | boolean | No | Set whether to enable automatic http trace, currently supports RCP automatic tracing, default is false |
| setHeaderHandler | HeaderHandler | No | Set the global FTTraceHeaderHandler, default is not set, used for custom Trace Header |
RUM User Data Tracking¶
View¶
Usage¶
startView(viewName: string, property?: Record<string, object>): void
stopView(property?: Record<string, object>): void
updateLoadTime(loadTime: number): void
Code Example¶
import { FTRUMGlobalManager } from 'ft_sdk/src/main/ets/components/rum/FTRUMGlobalManager';
// Scenario 1
FTRUMGlobalManager.getInstance().startView('ProductPage');
// Scenario 2: Dynamic parameters
// PropertyWrapper class must be defined (ArkTS requirement)
class PropertyWrapper {
value: string | number | boolean;
constructor(v: string | number | boolean) {
this.value = v;
}
}
class ViewProperty {
private properties: Map<string, string | number | boolean> = new Map();
set(key: string, value: string | number | boolean): void {
this.properties.set(key, value);
}
toObject(): Record<string, object> {
const result: Record<string, object> = {} as Record<string, object>;
this.properties.forEach((value, key) => {
result[key] = new PropertyWrapper(value) as object;
});
return result;
}
}
const viewProperty = new ViewProperty();
viewProperty.set('page_category', 'product');
viewProperty.set('page_id', '12345');
FTRUMGlobalManager.getInstance().startView('ProductPage', viewProperty.toObject());
FTRUMGlobalManager.getInstance().updateLoadTime(100000000);
// Scenario 1
FTRUMGlobalManager.getInstance().stopView();
// Scenario 2: Dynamic parameters
const stopViewProperty = new ViewProperty();
stopViewProperty.set('view_duration', 1000);
FTRUMGlobalManager.getInstance().stopView(stopViewProperty.toObject());
Action¶
Usage¶
addAction(actionName: string, actionType: string, duration: number, startTime: number, property?: Record<string, object>): void
Code Example¶
import { FTRUMGlobalManager } from 'ft_sdk/src/main/ets/components/rum/FTRUMGlobalManager';
// Scenario 1: Direct completion
FTRUMGlobalManager.getInstance().addAction('buy_button_click', 'click');
// Scenario 2: Dynamic parameters
// PropertyWrapper class must be defined (ArkTS requirement)
class PropertyWrapper {
value: string | number | boolean;
constructor(v: string | number | boolean) {
this.value = v;
}
}
class ActionProperty {
private properties: Map<string, string | number | boolean> = new Map();
set(key: string, value: string | number | boolean): void {
this.properties.set(key, value);
}
toObject(): Record<string, object> {
const result: Record<string, object> = {} as Record<string, object>;
this.properties.forEach((value, key) => {
result[key] = new PropertyWrapper(value) as object;
});
return result;
}
}
const actionProperty = new ActionProperty();
actionProperty.set('product_id', '12345');
actionProperty.set('product_name', 'iPhone 15');
FTRUMGlobalManager.getInstance().addActionWithProperty('buy_button_click', 'click', actionProperty.toObject());
// Scenario 3: Start Action (includes duration calculation mechanism)
FTRUMGlobalManager.getInstance().startAction('buy_button_click', 'click');
// ... Perform operation ...
FTRUMGlobalManager.getInstance().stopAction();
Automatic Tracking:
After enabling enableTraceUserAction in the RUM configuration, the SDK will automatically track UI component click events. Add customProperty to the Button component to get the button text:
Button('Buy Now')
.id('buy-btn')
.customProperty('buy-btn', 'Buy Now')
.onClick(() => {
// Click events will be automatically tracked
})
Resource¶
Usage¶
startResource(resourceId: string, property?: Record<string, object>): void
stopResource(resourceId: string): void
addResource(resourceId: string, resourceParams: ResourceParams, netStatusBean?: NetStatusBean): void
Automatic Tracking:
After enabling enableTraceUserResource in the RUM configuration, the SDK will automatically track HTTP requests sent via RCP.
Code Example¶
import { FTRUMGlobalManager } from 'ft_sdk/src/main/ets/components/rum/FTRUMGlobalManager';
import { ResourceParams } from 'ft_sdk/src/main/ets/components/rum/bean/ResourceParams';
import { NetStatusBean } from 'ft_sdk/src/main/ets/components/rum/bean/NetStatusBean';
const resourceId = 'https://api.example.com/data';
// Start resource tracking
FTRUMGlobalManager.getInstance().startResource(resourceId);
const resourceParams = new ResourceParams();
resourceParams.setUrl(resourceId);
resourceParams.setResourceStatus(200);
resourceParams.setResponseContentLength(1024);
resourceParams.resourceType = 'xhr';
const netStatusBean = new NetStatusBean();
netStatusBean.setResourceHostIP('192.168.1.1');
netStatusBean.setDNSTime(10000000);
netStatusBean.setTcpTime(20000000);
netStatusBean.setTTFB(50000000);
netStatusBean.setResponseTime(100000000);
// End resource tracking and add resource data
FTRUMGlobalManager.getInstance().stopResource(resourceId);
FTRUMGlobalManager.getInstance().addResource(resourceId, resourceParams, netStatusBean);
Error¶
Usage¶
addError(log: string, message: string, errorType: ErrorType, appState: AppState, property?: Record<string, object>): void
Code Example¶
import { FTRUMGlobalManager } from 'ft_sdk/src/main/ets/components/rum';
import { ErrorType, AppState } from 'ft_sdk/src/main/ets/components/rum';
const rumManager = FTRUMGlobalManager.getInstance();
// Option 1: Use ErrorProperty class (recommended for ArkTS)
// Note: ErrorProperty is a helper class, you can create your own or use Map
// PropertyWrapper class must be defined outside the method (ArkTS requirement)
class PropertyWrapper {
value: string | number | boolean;
constructor(v: string | number | boolean) {
this.value = v;
}
}
class ErrorProperty {
private properties: Map<string, string | number | boolean> = new Map();
set(key: string, value: string | number | boolean): void {
this.properties.set(key, value);
}
toObject(): Record<string, object> {
const result: Record<string, object> = {} as Record<string, object>;
this.properties.forEach((value, key) => {
// Create wrapper object using class (ArkTS requirement)
result[key] = new PropertyWrapper(value) as object;
});
return result;
}
}
const property = new ErrorProperty();
property.set('error_code', 'ERR_001');
property.set('user_id', 'test_user');
rumManager.addError(
'Error stack trace:\n at ProductPage.onClick\n at Button.onClick',
'Failed to load product data',
ErrorType.CUSTOM,
AppState.RUN,
property.toObject()
);
// Option 2: Pass null if no properties needed
rumManager.addError(
'Error stack trace:\n at ProductPage.onClick\n at Button.onClick',
'Failed to load product data',
ErrorType.CUSTOM,
AppState.RUN
);
Automatic Tracking:
After enabling enableTrackAppCrash and enableTrackAppANR in the RUM configuration, the SDK will automatically capture application crashes and ANR events.
LongTask¶
Usage¶
addLongTask(taskName: string, duration: number, property?: Record<string, string | number | boolean>): void
Code Example¶
import { FTRUMGlobalManager } from 'ft_sdk/src/main/ets/components/rum';
const rumManager = FTRUMGlobalManager.getInstance();
// Option 1: Use Map for properties (recommended for ArkTS)
const property = new Map<string, string | number | boolean>();
property.set('task_type', 'data_processing');
property.set('data_size', 10000);
// Convert Map to Record (ArkTS compatible)
const propertyRecord: Record<string, string | number | boolean> = {} as Record<string, string | number | boolean>;
property.forEach((value, key) => {
propertyRecord[key] = value;
});
const duration = 500000000;
rumManager.addLongTask('Data Processing', duration, propertyRecord);
// Option 2: Pass empty object or omit property parameter
rumManager.addLongTask('Data Processing', duration);
Logging Functionality¶
Custom Logs¶
Usage¶
FTLogger.getInstance().logBackground(content: string, status: Status): void
FTLogger.getInstance().logBackground(content: string, status: Status, property?: Map<string, object>): void
FTLogger.getInstance().logBackground(content: string, status: string, property?: Map<string, object>): void
Code Example¶
import { FTLogger } from 'ft_sdk/src/main/ets/components/log/FTLogger';
import { Status } from 'ft_sdk/src/main/ets/components/bean/Status';
// Upload a single log
FTLogger.getInstance().logBackground('Failed to load product data', Status.ERROR);
// Pass parameters
const property = new Map<string, object>();
property.set('error_code', 'ERR_001');
property.set('user_id', 'test_user');
FTLogger.getInstance().logBackground('Network request timeout', Status.WARN, property);
// Use string status
FTLogger.getInstance().logBackground('User logged in successfully', 'info');
Note:
- FTLogger is a public logging API provided to external users
- To enable SDK internal debug logs, use the FTSDKConfig.setDebug(true) configuration
Tracer Network Trace¶
After enabling enableAutoTrace in the Trace Configuration, the SDK will automatically inject trace headers into HTTP requests. You can also manually use FTTraceManager to add Propagation Header to HTTP requests.
Automatic Tracing¶
After enabling enableAutoTrace in the Trace configuration, the SDK will automatically inject Trace Headers into HTTP requests sent via RCP:
import http from '@ohos.net.http';
const httpRequest = http.createHttp();
const response = await httpRequest.request('https://api.example.com/data', {
method: http.RequestMethod.GET
});
// Trace Headers will be automatically injected into the request
Manually Obtain Trace Header¶
If using other network frameworks, you can manually obtain the Trace Header and add it to the request:
import { FTTraceManager } from 'ft_sdk/src/main/ets/components/trace/FTTraceManager';
import http from '@ohos.net.http';
const url = 'https://api.example.com/data';
const resourceId = 'unique-resource-id'; // Unique ID for identifying the resource
// Get trace header parameters
const traceHeaders: Record<string, string> = FTTraceManager.getInstance().getTraceHeader(resourceId, url);
// Add Trace Headers to the request
const httpRequest = http.createHttp();
const response = await httpRequest.request(url, {
method: http.RequestMethod.GET,
header: traceHeaders
});
Trace Types¶
| Trace Type | Protocol | Main Headers |
|---|---|---|
DDTRACE |
DataDog Trace | x-datadog-trace-id, x-datadog-parent-id, x-datadog-origin, x-datadog-sampling-priority |
ZIPKIN_MULTI_HEADER |
Zipkin Multi-Header | X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId, X-B3-Sampled |
ZIPKIN_SINGLE_HEADER |
Zipkin Single-Header (b3) | b3 |
JAEGER |
Jaeger | uber-trace-id |
TRACEPARENT |
W3C Trace Context | traceparent, tracestate |
SKYWALKING |
SkyWalking | sw8 |
User Data Binding¶
Usage¶
/**
* Bind user information (only user ID)
* @param id User ID
*/
static bindRumUserDataById(id: string): void
/**
* Bind user information (complete user data)
* @param userData User data object
*/
static async bindRumUserData(userData: UserData): Promise<void>
/**
* Unbind user information
*/
static async unbindRumUserData(): Promise<void>
UserData¶
| Method Name | Meaning | Required | Note |
|---|---|---|---|
| setId | Set user ID | No | |
| setName | Set username | No | |
| setEmail | Set email | No | |
| setExts | Set user extensions | No | Add rules refer to here |
Code Example¶
import { FTSDK } from 'ft_sdk/src/main/ets/components/FTSDK';
import { UserData } from 'ft_sdk/src/main/ets/components/rum/bean/UserData';
// Method 1: Only bind user ID (recommended for quick binding)
FTSDK.bindRumUserDataById('user_001');
// Method 2: Bind complete user information (recommended for scenarios requiring more user information)
const userData = new UserData();
userData.setId('user_001');
userData.setName('test.user');
userData.setEmail('test@mail.com');
const exts: Record<string, string> = {
'ft_key': 'ft_value',
'user_type': 'vip'
};
userData.setExts(exts);
await FTSDK.bindRumUserData(userData);
// Unbind user information (call when user logs out)
await FTSDK.unbindRumUserData();
Add Custom Tags¶
import { FTSDK } from 'ft_sdk/src/main/ets/components/FTSDK';
const globalContext = new Map<string, string>();
globalContext.set('platform', 'harmonyos');
globalContext.set('version', '1.0.0');
FTSDK.appendGlobalContext(globalContext);
const rumGlobalContext = new Map<string, string>();
rumGlobalContext.set('user_type', 'vip');
rumGlobalContext.set('channel', 'official');
FTSDK.appendRUMGlobalContext(rumGlobalContext);
Actively Synchronize Data¶
When configuring autoSync to false, you need to actively trigger data synchronization:
Clear SDK Cache Data¶
Note: clearAllData() will delete all unsynchronized cache data, including:
- All data in the sync data table (sync_data_flat)
- All data in the RUM view data table (rum_view)
- All data in the RUM action data table (rum_action)
WebView Integration¶
After enabling enableTraceWebView in the RUM configuration, the SDK will automatically collect RUM data in WebView.
Use in WebView Pages
import { FTWebViewHandler } from 'ft_sdk/src/main/ets/components/web/FTWebViewHandler';
import { webview } from '@kit.ArkWeb';
@Component
export struct MyWebViewPage {
@State webController: webview.WebviewController = new webview.WebviewController();
private webViewHandler: FTWebViewHandler = new FTWebViewHandler();
aboutToAppear() {
// Only need to pass in webController, the configuration will be read from the RUM configuration in the application entry
this.webViewHandler.setWebView(this.webController);
}
aboutToDisappear() {
this.webViewHandler.clearWebController();
}
build() {
Web({
src: 'https://example.com',
controller: this.webController
})
.javaScriptAccess(true)
.javaScriptProxy(this.webViewHandler.getJavaScriptProxy(this.webController))
}
}