@@ -3,6 +3,7 @@ package connector
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "net/http"
89 "strings"
@@ -227,6 +228,7 @@ func (ll *LineEmailLogin) StartWithOverride(ctx context.Context, override *bridg
227228
228229 res , err := loginWithCredentials (ll .Email , ll .Password , ll .Certificate )
229230 if err != nil {
231+ ll .logLoginFailure (err , "reconnect" )
230232 reason := loginErrorReason (err )
231233 if reason == "" {
232234 reason = fmt .Sprintf ("Login failed: %v" , err )
@@ -253,6 +255,7 @@ func (ll *LineEmailLogin) SubmitUserInput(ctx context.Context, input map[string]
253255
254256 res , err := loginWithCredentials (ll .Email , ll .Password , "" )
255257 if err != nil {
258+ ll .logLoginFailure (err , "credentials" )
256259 reason := loginErrorReason (err )
257260 if reason == "" {
258261 reason = fmt .Sprintf ("Login failed: %v" , err )
@@ -299,28 +302,140 @@ func loginErrorInstructions(message string) string {
299302 return fmt .Sprintf ("Could not log in to LINE: %s" , message )
300303}
301304
302- func loginErrorReason (err error ) string {
305+ type loginErrorDetails struct {
306+ HTTPStatus int
307+ ResponseCode int
308+ ResponseMessage string
309+ ErrorName string
310+ ErrorCode int
311+ ErrorMessage string
312+ ErrorReason string
313+ HasHTTPStatus bool
314+ HasResponseCode bool
315+ HasErrorCode bool
316+ HasResponseFields bool
317+ }
318+
319+ func parseLoginErrorDetails (err error ) loginErrorDetails {
320+ var details loginErrorDetails
303321 if err == nil {
304- return ""
322+ return details
305323 }
324+
306325 msg := err .Error ()
326+ if apiErrorIndex := strings .Index (msg , "API error " ); apiErrorIndex >= 0 {
327+ if parsed , scanErr := fmt .Sscanf (msg [apiErrorIndex :], "API error %d:" , & details .HTTPStatus ); parsed == 1 && scanErr == nil {
328+ details .HasHTTPStatus = true
329+ }
330+ }
331+
307332 start := strings .Index (msg , "{" )
308333 end := strings .LastIndex (msg , "}" )
309334 if start == - 1 || end == - 1 || end <= start {
310- return ""
335+ return details
311336 }
337+
312338 var payload struct {
313- Data struct {
314- Message string `json:"message"`
315- Reason string `json:"reason"`
316- } `json:"data"`
339+ Code * int `json:"code"`
340+ Message string `json:"message"`
341+ Data json.RawMessage `json:"data"`
342+ }
343+ if json .Unmarshal ([]byte (msg [start :end + 1 ]), & payload ) != nil {
344+ return details
345+ }
346+
347+ details .HasResponseFields = true
348+ details .ResponseMessage = payload .Message
349+ if payload .Code != nil {
350+ details .ResponseCode = * payload .Code
351+ details .HasResponseCode = true
352+ }
353+
354+ var responseError struct {
355+ Name string `json:"name"`
356+ Code * int `json:"code"`
357+ Message string `json:"message"`
358+ Reason string `json:"reason"`
359+ }
360+ if len (payload .Data ) > 0 && json .Unmarshal (payload .Data , & responseError ) == nil {
361+ details .ErrorName = responseError .Name
362+ details .ErrorMessage = responseError .Message
363+ details .ErrorReason = responseError .Reason
364+ if responseError .Code != nil {
365+ details .ErrorCode = * responseError .Code
366+ details .HasErrorCode = true
367+ }
317368 }
318- if err := json .Unmarshal ([]byte (msg [start :end + 1 ]), & payload ); err != nil {
369+ return details
370+ }
371+
372+ func loginErrorSummary (err error , details loginErrorDetails ) string {
373+ if err == nil {
319374 return ""
320375 }
321- reason := payload .Data .Reason
376+ if details .HasHTTPStatus {
377+ return fmt .Sprintf ("API error %d" , details .HTTPStatus )
378+ }
379+ if errors .Is (err , context .DeadlineExceeded ) {
380+ return "request timed out"
381+ }
382+ if errors .Is (err , context .Canceled ) {
383+ return "request canceled"
384+ }
385+ return ""
386+ }
387+
388+ func loginLogField (value string ) string {
389+ value = strings .TrimSpace (value )
390+ const maxRunes = 512
391+ runes := []rune (value )
392+ if len (runes ) > maxRunes {
393+ return string (runes [:maxRunes ]) + "…"
394+ }
395+ return value
396+ }
397+
398+ func (ll * LineEmailLogin ) logLoginFailure (err error , flow string ) {
399+ if err == nil || ll .User == nil {
400+ return
401+ }
402+
403+ details := parseLoginErrorDetails (err )
404+ event := ll .User .Log .Warn ().
405+ Str ("login_flow" , flow ).
406+ Bool ("has_certificate" , ll .Certificate != "" )
407+ if summary := loginErrorSummary (err , details ); summary != "" {
408+ event .Str ("error_summary" , summary )
409+ }
410+ if details .HasHTTPStatus {
411+ event .Int ("http_status" , details .HTTPStatus )
412+ }
413+ if details .HasResponseCode {
414+ event .Int ("line_response_code" , details .ResponseCode )
415+ }
416+ if details .ResponseMessage != "" {
417+ event .Str ("line_response_message" , loginLogField (details .ResponseMessage ))
418+ }
419+ if details .ErrorName != "" {
420+ event .Str ("line_error_name" , loginLogField (details .ErrorName ))
421+ }
422+ if details .HasErrorCode {
423+ event .Int ("line_error_code" , details .ErrorCode )
424+ }
425+ if details .ErrorMessage != "" {
426+ event .Str ("line_error_message" , loginLogField (details .ErrorMessage ))
427+ }
428+ if details .ErrorReason != "" {
429+ event .Str ("line_error_reason" , loginLogField (details .ErrorReason ))
430+ }
431+ event .Msg ("LINE login attempt failed" )
432+ }
433+
434+ func loginErrorReason (err error ) string {
435+ details := parseLoginErrorDetails (err )
436+ reason := details .ErrorReason
322437 if reason == "" {
323- reason = payload . Data . Message
438+ reason = details . ErrorMessage
324439 }
325440 if isBlockedUserLoginError (reason ) {
326441 return loginTooManyAttemptsReason
@@ -341,6 +456,7 @@ func (ll *LineEmailLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error)
341456 }
342457 return nil , fmt .Errorf ("verification failed: no auth token received" )
343458 case err := <- ll .pollErr :
459+ ll .logLoginFailure (err , "verification_poll" )
344460 return nil , fmt .Errorf ("verification failed: %w" , err )
345461 case <- ctx .Done ():
346462 return nil , ctx .Err ()
@@ -350,6 +466,7 @@ func (ll *LineEmailLogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error)
350466 if ll .AwaitingPIN {
351467 res , err := loginWithCredentials (ll .Email , ll .Password , ll .Certificate )
352468 if err != nil {
469+ ll .logLoginFailure (err , "pin_continuation" )
353470 return nil , fmt .Errorf ("login failed: %w" , err )
354471 }
355472 return ll .handleLoginResponse (ctx , res )
0 commit comments