Skip to content
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
49 changes: 49 additions & 0 deletions packages/pic/src/management-canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,52 @@ export function encodeUpdateCanisterSettingsRequest(
): Uint8Array {
return new Uint8Array(IDL.encode([UpdateCanisterSettingsRequest], [arg]));
}

const CanisterLogRecord = IDL.Record({
idx: IDL.Nat64,
timestamp_nanos: IDL.Nat64,
content: IDL.Vec(IDL.Nat8),
});

export interface CanisterLogRecord {
idx: bigint;
timestamp_nanos: bigint;
content: Uint8Array;
}

const FetchCanisterLogsRequest = IDL.Record({
canister_id: IDL.Principal,
});

export interface FetchCanisterLogsRequest {
canister_id: Principal;
}

export function encodeFetchCanisterLogsRequest(
arg: FetchCanisterLogsRequest,
): Uint8Array {
return new Uint8Array(IDL.encode([FetchCanisterLogsRequest], [arg]));
}

const FetchCanisterLogsResponse = IDL.Record({
canister_log_records: IDL.Vec(CanisterLogRecord),
});

export interface FetchCanisterLogsResponse {
canister_log_records: CanisterLogRecord[];
}

export function decodeFetchCanisterLogsResponse(
arg: Uint8Array,
): FetchCanisterLogsResponse {
const payload = decodeCandid<FetchCanisterLogsResponse>(
[FetchCanisterLogsResponse],
arg,
);

if (isNil(payload)) {
throw new Error('Failed to decode FetchCanisterLogsResponse');
}

return payload;
}
43 changes: 43 additions & 0 deletions packages/pic/src/pocket-ic-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -920,3 +920,46 @@ export interface HttpsOutcallRejectResponse {
}

//#endregion HTTPS Outcalls

//#region FetchCanisterLogs

/**
* Options for fetching canister logs.
*
* @category Types
* @see [Principal](https://js.icp.build/core/latest/libs/principal/api/classes/principal/)
*/
export interface FetchCanisterLogsOptions {
/**
* The Principal of the canister to fetch logs for.
*/
canisterId: Principal;

/**
* The Principal to send the request as.
* Defaults to the anonymous principal.
*/
sender?: Principal;
}

/**
* A canister log record.
*/
export interface CanisterLogRecord {
/**
* The index of the log record.
*/
idx: bigint;

/**
* The timestamp of the log record in nanoseconds since epoch.
*/
timestampNanos: bigint;

/**
* The content of the log record.
*/
content: Uint8Array;
}

//#endregion FetchCanisterLogs
35 changes: 35 additions & 0 deletions packages/pic/src/pocket-ic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
UpdateCallOptions,
PendingHttpsOutcall,
MockPendingHttpsOutcallOptions,
FetchCanisterLogsOptions,
CanisterLogRecord as CanisterLogRecordPublic,
} from './pocket-ic-types';
import {
MANAGEMENT_CANISTER_ID,
Expand All @@ -28,6 +30,8 @@ import {
encodeInstallCodeRequest,
encodeStartCanisterRequest,
encodeUpdateCanisterSettingsRequest,
encodeFetchCanisterLogsRequest,
decodeFetchCanisterLogsResponse,
} from './management-canister';
import {
createDeferredActorClass,
Expand Down Expand Up @@ -694,6 +698,37 @@ export class PocketIc {
return res.body;
}

/**
* Fetches the logs for the given canister.
*
* @param options Options for fetching canister logs, see {@link FetchCanisterLogsOptions}.
* @returns An array of {@link CanisterLogRecord} entries.
*/
public async fetchCanisterLogs({
canisterId,
sender = Principal.anonymous(),
}: FetchCanisterLogsOptions): Promise<CanisterLogRecordPublic[]> {
const payload = encodeFetchCanisterLogsRequest({
canister_id: canisterId,
});

const res = await this.client.queryCall({
canisterId: MANAGEMENT_CANISTER_ID,
sender,
method: 'fetch_canister_logs',
payload,
effectivePrincipal: { canisterId },
});

const decoded = decodeFetchCanisterLogsResponse(res.body);

return decoded.canister_log_records.map(record => ({
idx: record.idx,
timestampNanos: record.timestamp_nanos,
content: new Uint8Array(record.content),
}));
}

/**
* Makes an update call to the given canister.
*
Expand Down
42 changes: 42 additions & 0 deletions packages/pic/tests/src/fetchCanisterLogs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { CONTROLLER, TestFixture } from './util';

describe('fetchCanisterLogs', () => {
let fixture: TestFixture;

beforeEach(async () => {
fixture = await TestFixture.create();
});

afterEach(async () => {
await fixture.tearDown();
});

it('should fetch canister logs', async () => {
const message = 'Hello from canister';

await fixture.actor.print_log(message);

const logs = await fixture.pic.fetchCanisterLogs({
canisterId: fixture.canisterId,
sender: CONTROLLER.getPrincipal(),
});

expect(logs.length).toBeGreaterThanOrEqual(1);

const lastLog = logs[logs.length - 1];
const logContent = new TextDecoder().decode(lastLog.content);

expect(logContent).toBe(message);
expect(lastLog.idx).toBeDefined();
expect(lastLog.timestampNanos).toBeDefined();
});

it('should return empty logs for a canister with no logs', async () => {
const logs = await fixture.pic.fetchCanisterLogs({
canisterId: fixture.canisterId,
sender: CONTROLLER.getPrincipal(),
});

expect(logs).toEqual([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { ActorMethod } from '@dfinity/agent';
import type { IDL } from '@dfinity/candid';

export type Time = bigint;
export interface _SERVICE { 'get_time' : ActorMethod<[], Time> }
export interface _SERVICE {
'get_time' : ActorMethod<[], Time>,
'print_log' : ActorMethod<[string], undefined>,
}
export declare const idlFactory: IDL.InterfaceFactory;
export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export const idlFactory = ({ IDL }) => {
const Time = IDL.Int;
return IDL.Service({ 'get_time' : IDL.Func([], [Time], ['query']) });
return IDL.Service({
'get_time' : IDL.Func([], [Time], ['query']),
'print_log' : IDL.Func([IDL.Text], [], []),
});
};
export const init = ({ IDL }) => { return []; };
5 changes: 5 additions & 0 deletions packages/pic/tests/test-canister/main.mo
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import Time "mo:base/Time";
import Debug "mo:base/Debug";

persistent actor TestCanister {
public query func get_time() : async Time.Time {
return Time.now();
};

public func print_log(message : Text) : async () {
Debug.print(message);
};
};
Binary file modified packages/pic/tests/test-canister/test_canister.wasm.gz
Binary file not shown.