@@ -5,6 +5,7 @@ import assert from 'node:assert';
55import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services' ;
66import {
77 AccountId ,
8+ Client ,
89 EthereumTransaction ,
910 ExchangeRate ,
1011 FileAppendTransaction ,
@@ -13,6 +14,8 @@ import {
1314 FileId ,
1415 FileInfoQuery ,
1516 Hbar ,
17+ Logger as HederaLogger ,
18+ LogLevel ,
1619 Query ,
1720 Status ,
1821 TransactionId ,
@@ -197,6 +200,132 @@ describe('SdkClient', async function () {
197200 } ) ;
198201 } ) ;
199202
203+ describe ( 'SDK Logger Configuration' , ( ) => {
204+ // Simple helper to create standard mock logger
205+ const createMockLogger = ( additionalProps = { } ) => ( {
206+ child : sinon . stub ( ) . returns ( { name : 'sdk-client' } ) ,
207+ info : sinon . stub ( ) ,
208+ ...additionalProps ,
209+ } ) ;
210+
211+ // Simple helper to create Client stub
212+ const createClientStub = ( ) =>
213+ sinon . stub ( Client , 'forName' ) . returns ( {
214+ setOperator : sinon . stub ( ) . returnsThis ( ) ,
215+ setTransportSecurity : sinon . stub ( ) . returnsThis ( ) ,
216+ setRequestTimeout : sinon . stub ( ) . returnsThis ( ) ,
217+ setLogger : sinon . stub ( ) . returnsThis ( ) ,
218+ setMaxExecutionTime : sinon . stub ( ) . returnsThis ( ) ,
219+ operatorAccountId : null ,
220+ operatorPublicKey : null ,
221+ } as any ) ;
222+
223+ // Simple helper to restore stubs
224+ const cleanupStubs = ( ...stubs : sinon . SinonStub [ ] ) => {
225+ stubs . forEach ( ( stub ) => stub . restore ( ) ) ;
226+ } ;
227+
228+ it ( 'should create child logger with "sdk-client" name using SDK_LOG_LEVEL' , ( ) => {
229+ const mockLogger = createMockLogger ( ) ;
230+
231+ const configStub = sinon . stub ( ConfigService , 'get' ) ;
232+ configStub . withArgs ( 'SDK_LOG_LEVEL' ) . returns ( 'debug' ) ;
233+ configStub . callThrough ( ) ;
234+
235+ const clientStub = createClientStub ( ) ;
236+
237+ try {
238+ const eventEmitter = new EventEmitter < TypedEvents > ( ) ;
239+ new SDKClient ( 'testnet' , mockLogger as any , eventEmitter , hbarLimitService ) ;
240+
241+ expect ( mockLogger . child . calledWith ( { name : 'sdk-client' } , { level : 'debug' } ) ) . to . be . true ;
242+ } finally {
243+ cleanupStubs ( configStub , clientStub ) ;
244+ }
245+ } ) ;
246+
247+ it ( 'should use SDK_LOG_LEVEL independently from global LOG_LEVEL' , ( ) => {
248+ const mockLogger = createMockLogger ( { level : 'error' } ) ;
249+
250+ const configStub = sinon . stub ( ConfigService , 'get' ) ;
251+ configStub . withArgs ( 'SDK_LOG_LEVEL' ) . returns ( 'info' ) ;
252+ configStub . withArgs ( 'LOG_LEVEL' ) . returns ( 'error' ) ;
253+ configStub . callThrough ( ) ;
254+
255+ const clientStub = createClientStub ( ) ;
256+
257+ try {
258+ const eventEmitter = new EventEmitter < TypedEvents > ( ) ;
259+ new SDKClient ( 'testnet' , mockLogger as any , eventEmitter , hbarLimitService ) ;
260+
261+ // Verify SDK logger uses SDK_LOG_LEVEL ('info'), not global LOG_LEVEL ('error')
262+ expect ( mockLogger . child . calledWith ( { name : 'sdk-client' } , { level : 'info' } ) ) . to . be . true ;
263+ } finally {
264+ cleanupStubs ( configStub , clientStub ) ;
265+ }
266+ } ) ;
267+
268+ it ( 'should create child logger that inherits from global logger with SDK log level override' , ( ) => {
269+ const mockGlobalLogger = createMockLogger ( {
270+ level : 'trace' ,
271+ service : 'hedera-relay' , // Global logger has this binding
272+ } ) ;
273+
274+ // Mock child() to simulate pino inheritance behavior
275+ mockGlobalLogger . child . callsFake ( ( newBindings , options ) => ( {
276+ service : 'hedera-relay' , // Inherited from parent
277+ name : newBindings . name , // Added by child
278+ level : options . level , // Overridden by child options
279+ } ) ) ;
280+
281+ const configStub = sinon . stub ( ConfigService , 'get' ) ;
282+ configStub . withArgs ( 'SDK_LOG_LEVEL' ) . returns ( 'warn' ) ;
283+ configStub . callThrough ( ) ;
284+
285+ const clientStub = createClientStub ( ) ;
286+
287+ try {
288+ const eventEmitter = new EventEmitter < TypedEvents > ( ) ;
289+ new SDKClient ( 'testnet' , mockGlobalLogger as any , eventEmitter , hbarLimitService ) ;
290+
291+ // Verify child() was called correctly
292+ expect ( mockGlobalLogger . child . calledWith ( { name : 'sdk-client' } , { level : 'warn' } ) ) . to . be . true ;
293+
294+ // Verify the child logger has the right properties
295+ const childLogger = mockGlobalLogger . child . returnValues [ 0 ] ;
296+ expect ( childLogger . service ) . to . equal ( 'hedera-relay' ) ; // Inherited
297+ expect ( childLogger . name ) . to . equal ( 'sdk-client' ) ; // Added by child
298+ expect ( childLogger . level ) . to . equal ( 'warn' ) ; // Overridden level
299+ } finally {
300+ cleanupStubs ( configStub , clientStub ) ;
301+ }
302+ } ) ;
303+
304+ it ( 'should work across different log levels' , ( ) => {
305+ const testLevels = [ 'trace' , 'debug' , 'info' , 'warn' , 'error' ] ;
306+
307+ testLevels . forEach ( ( level ) => {
308+ const mockLogger = createMockLogger ( ) ;
309+
310+ const configStub = sinon . stub ( ConfigService , 'get' ) ;
311+ configStub . withArgs ( 'SDK_LOG_LEVEL' ) . returns ( level ) ;
312+ configStub . callThrough ( ) ;
313+
314+ const clientStub = createClientStub ( ) ;
315+
316+ try {
317+ const eventEmitter = new EventEmitter < TypedEvents > ( ) ;
318+ new SDKClient ( 'testnet' , mockLogger as any , eventEmitter , hbarLimitService ) ;
319+
320+ // Verify child logger is always created with 'sdk-client' name
321+ expect ( mockLogger . child . calledWith ( { name : 'sdk-client' } , { level } ) ) . to . be . true ;
322+ } finally {
323+ cleanupStubs ( configStub , clientStub ) ;
324+ }
325+ } ) ;
326+ } ) ;
327+ } ) ;
328+
200329 describe ( 'submitEthereumTransaction' , ( ) => {
201330 const accountId = AccountId . fromString ( '0.0.1234' ) ;
202331 const chunkSize = ConfigService . get ( 'FILE_APPEND_CHUNK_SIZE' ) ;
0 commit comments