github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/log/table.go (about) 1 package log 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 8 "github.com/ydb-platform/ydb-go-sdk/v3/retry" 9 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 10 ) 11 12 // Table makes trace.Table with logging events from details 13 func Table(l Logger, d trace.Detailer, opts ...Option) (t trace.Table) { 14 return internalTable(wrapLogger(l, opts...), d) 15 } 16 17 //nolint:gocyclo 18 func internalTable(l *wrapper, d trace.Detailer) (t trace.Table) { 19 t.OnDo = func( 20 info trace.TableDoStartInfo, 21 ) func( 22 info trace.TableDoIntermediateInfo, 23 ) func( 24 trace.TableDoDoneInfo, 25 ) { 26 if d.Details()&trace.TablePoolAPIEvents == 0 { 27 return nil 28 } 29 ctx := with(*info.Context, TRACE, "ydb", "table", "do") 30 idempotent := info.Idempotent 31 label := info.Label 32 l.Log(ctx, "start", 33 Bool("idempotent", idempotent), 34 String("label", label), 35 ) 36 start := time.Now() 37 38 return func(info trace.TableDoIntermediateInfo) func(trace.TableDoDoneInfo) { 39 if info.Error == nil { 40 l.Log(ctx, "done", 41 latencyField(start), 42 Bool("idempotent", idempotent), 43 String("label", label), 44 ) 45 } else { 46 lvl := WARN 47 if !xerrors.IsYdb(info.Error) { 48 lvl = DEBUG 49 } 50 m := retry.Check(info.Error) 51 l.Log(WithLevel(ctx, lvl), "failed", 52 latencyField(start), 53 Bool("idempotent", idempotent), 54 String("label", label), 55 Error(info.Error), 56 Bool("retryable", m.MustRetry(idempotent)), 57 Int64("code", m.StatusCode()), 58 Bool("deleteSession", m.MustDeleteSession()), 59 versionField(), 60 ) 61 } 62 63 return func(info trace.TableDoDoneInfo) { 64 if info.Error == nil { 65 l.Log(ctx, "done", 66 latencyField(start), 67 Bool("idempotent", idempotent), 68 String("label", label), 69 Int("attempts", info.Attempts), 70 ) 71 } else { 72 lvl := ERROR 73 if !xerrors.IsYdb(info.Error) { 74 lvl = DEBUG 75 } 76 m := retry.Check(info.Error) 77 l.Log(WithLevel(ctx, lvl), "done", 78 latencyField(start), 79 Bool("idempotent", idempotent), 80 String("label", label), 81 Int("attempts", info.Attempts), 82 Error(info.Error), 83 Bool("retryable", m.MustRetry(idempotent)), 84 Int64("code", m.StatusCode()), 85 Bool("deleteSession", m.MustDeleteSession()), 86 versionField(), 87 ) 88 } 89 } 90 } 91 } 92 t.OnDoTx = func( 93 info trace.TableDoTxStartInfo, 94 ) func( 95 info trace.TableDoTxIntermediateInfo, 96 ) func( 97 trace.TableDoTxDoneInfo, 98 ) { 99 if d.Details()&trace.TablePoolAPIEvents == 0 { 100 return nil 101 } 102 ctx := with(*info.Context, TRACE, "ydb", "table", "do", "tx") 103 idempotent := info.Idempotent 104 label := info.Label 105 l.Log(ctx, "start", 106 Bool("idempotent", idempotent), 107 String("label", label), 108 ) 109 start := time.Now() 110 111 return func(info trace.TableDoTxIntermediateInfo) func(trace.TableDoTxDoneInfo) { 112 if info.Error == nil { 113 l.Log(ctx, "done", 114 latencyField(start), 115 Bool("idempotent", idempotent), 116 String("label", label), 117 ) 118 } else { 119 lvl := ERROR 120 if !xerrors.IsYdb(info.Error) { 121 lvl = DEBUG 122 } 123 m := retry.Check(info.Error) 124 l.Log(WithLevel(ctx, lvl), "done", 125 latencyField(start), 126 Bool("idempotent", idempotent), 127 String("label", label), 128 Error(info.Error), 129 Bool("retryable", m.MustRetry(idempotent)), 130 Int64("code", m.StatusCode()), 131 Bool("deleteSession", m.MustDeleteSession()), 132 versionField(), 133 ) 134 } 135 136 return func(info trace.TableDoTxDoneInfo) { 137 if info.Error == nil { 138 l.Log(ctx, "done", 139 latencyField(start), 140 Bool("idempotent", idempotent), 141 String("label", label), 142 Int("attempts", info.Attempts), 143 ) 144 } else { 145 lvl := WARN 146 if !xerrors.IsYdb(info.Error) { 147 lvl = DEBUG 148 } 149 m := retry.Check(info.Error) 150 l.Log(WithLevel(ctx, lvl), "done", 151 latencyField(start), 152 Bool("idempotent", idempotent), 153 String("label", label), 154 Int("attempts", info.Attempts), 155 Error(info.Error), 156 Bool("retryable", m.MustRetry(idempotent)), 157 Int64("code", m.StatusCode()), 158 Bool("deleteSession", m.MustDeleteSession()), 159 versionField(), 160 ) 161 } 162 } 163 } 164 } 165 t.OnCreateSession = func( 166 info trace.TableCreateSessionStartInfo, 167 ) func( 168 info trace.TableCreateSessionIntermediateInfo, 169 ) func( 170 trace.TableCreateSessionDoneInfo, 171 ) { 172 if d.Details()&trace.TablePoolAPIEvents == 0 { 173 return nil 174 } 175 ctx := with(*info.Context, TRACE, "ydb", "table", "create", "session") 176 l.Log(ctx, "start") 177 start := time.Now() 178 179 return func(info trace.TableCreateSessionIntermediateInfo) func(trace.TableCreateSessionDoneInfo) { 180 if info.Error == nil { 181 l.Log(ctx, "intermediate", 182 latencyField(start), 183 ) 184 } else { 185 l.Log(WithLevel(ctx, ERROR), "intermediate", 186 latencyField(start), 187 Error(info.Error), 188 versionField(), 189 ) 190 } 191 192 return func(info trace.TableCreateSessionDoneInfo) { 193 if info.Error == nil { 194 l.Log(ctx, "done", 195 latencyField(start), 196 Int("attempts", info.Attempts), 197 String("session_id", info.Session.ID()), 198 String("session_status", info.Session.Status()), 199 ) 200 } else { 201 l.Log(WithLevel(ctx, ERROR), "failed", 202 latencyField(start), 203 Int("attempts", info.Attempts), 204 Error(info.Error), 205 versionField(), 206 ) 207 } 208 } 209 } 210 } 211 t.OnSessionNew = func(info trace.TableSessionNewStartInfo) func(trace.TableSessionNewDoneInfo) { 212 if d.Details()&trace.TableSessionEvents == 0 { 213 return nil 214 } 215 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "new") 216 l.Log(ctx, "start") 217 start := time.Now() 218 219 return func(info trace.TableSessionNewDoneInfo) { 220 if info.Error == nil { 221 if info.Session != nil { 222 l.Log(ctx, "done", 223 latencyField(start), 224 String("id", info.Session.ID()), 225 ) 226 } else { 227 l.Log(WithLevel(ctx, WARN), "failed", 228 latencyField(start), 229 versionField(), 230 ) 231 } 232 } else { 233 l.Log(WithLevel(ctx, WARN), "failed", 234 latencyField(start), 235 Error(info.Error), 236 versionField(), 237 ) 238 } 239 } 240 } 241 t.OnSessionDelete = func(info trace.TableSessionDeleteStartInfo) func(trace.TableSessionDeleteDoneInfo) { 242 if d.Details()&trace.TableSessionEvents == 0 { 243 return nil 244 } 245 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "delete") 246 session := info.Session 247 l.Log(ctx, "start", 248 String("id", info.Session.ID()), 249 String("status", info.Session.Status()), 250 ) 251 start := time.Now() 252 253 return func(info trace.TableSessionDeleteDoneInfo) { 254 if info.Error == nil { 255 l.Log(ctx, "done", 256 latencyField(start), 257 String("id", session.ID()), 258 String("status", session.Status()), 259 ) 260 } else { 261 l.Log(WithLevel(ctx, WARN), "failed", 262 latencyField(start), 263 String("id", session.ID()), 264 String("status", session.Status()), 265 Error(info.Error), 266 versionField(), 267 ) 268 } 269 } 270 } 271 t.OnSessionKeepAlive = func(info trace.TableKeepAliveStartInfo) func(trace.TableKeepAliveDoneInfo) { 272 if d.Details()&trace.TableSessionEvents == 0 { 273 return nil 274 } 275 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "keep", "alive") 276 session := info.Session 277 l.Log(ctx, "start", 278 String("id", session.ID()), 279 String("status", session.Status()), 280 ) 281 start := time.Now() 282 283 return func(info trace.TableKeepAliveDoneInfo) { 284 if info.Error == nil { 285 l.Log(ctx, "done", 286 latencyField(start), 287 String("id", session.ID()), 288 String("status", session.Status()), 289 ) 290 } else { 291 l.Log(WithLevel(ctx, WARN), "failed", 292 latencyField(start), 293 String("id", session.ID()), 294 String("status", session.Status()), 295 Error(info.Error), 296 versionField(), 297 ) 298 } 299 } 300 } 301 t.OnSessionQueryPrepare = func( 302 info trace.TablePrepareDataQueryStartInfo, 303 ) func( 304 trace.TablePrepareDataQueryDoneInfo, 305 ) { 306 if d.Details()&trace.TableSessionQueryInvokeEvents == 0 { 307 return nil 308 } 309 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "query", "prepare") 310 session := info.Session 311 query := info.Query 312 l.Log(ctx, "start", 313 appendFieldByCondition(l.logQuery, 314 String("query", info.Query), 315 String("id", session.ID()), 316 String("status", session.Status()), 317 )..., 318 ) 319 start := time.Now() 320 321 return func(info trace.TablePrepareDataQueryDoneInfo) { 322 if info.Error == nil { 323 l.Log(ctx, "done", 324 appendFieldByCondition(l.logQuery, 325 Stringer("result", info.Result), 326 appendFieldByCondition(l.logQuery, 327 String("query", query), 328 String("id", session.ID()), 329 String("status", session.Status()), 330 latencyField(start), 331 )..., 332 )..., 333 ) 334 } else { 335 l.Log(WithLevel(ctx, ERROR), "failed", 336 appendFieldByCondition(l.logQuery, 337 String("query", query), 338 Error(info.Error), 339 String("id", session.ID()), 340 String("status", session.Status()), 341 latencyField(start), 342 versionField(), 343 )..., 344 ) 345 } 346 } 347 } 348 t.OnSessionQueryExecute = func( 349 info trace.TableExecuteDataQueryStartInfo, 350 ) func( 351 trace.TableExecuteDataQueryDoneInfo, 352 ) { 353 if d.Details()&trace.TableSessionQueryInvokeEvents == 0 { 354 return nil 355 } 356 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "query", "execute") 357 session := info.Session 358 query := info.Query 359 l.Log(ctx, "start", 360 appendFieldByCondition(l.logQuery, 361 Stringer("query", info.Query), 362 String("id", session.ID()), 363 String("status", session.Status()), 364 )..., 365 ) 366 start := time.Now() 367 368 return func(info trace.TableExecuteDataQueryDoneInfo) { 369 if info.Error == nil { 370 tx := info.Tx 371 l.Log(ctx, "done", 372 appendFieldByCondition(l.logQuery, 373 Stringer("query", query), 374 String("id", session.ID()), 375 String("tx", tx.ID()), 376 String("status", session.Status()), 377 Bool("prepared", info.Prepared), 378 NamedError("result_err", info.Result.Err()), 379 latencyField(start), 380 )..., 381 ) 382 } else { 383 l.Log(WithLevel(ctx, ERROR), "failed", 384 appendFieldByCondition(l.logQuery, 385 Stringer("query", query), 386 Error(info.Error), 387 String("id", session.ID()), 388 String("status", session.Status()), 389 Bool("prepared", info.Prepared), 390 latencyField(start), 391 versionField(), 392 )..., 393 ) 394 } 395 } 396 } 397 t.OnSessionQueryStreamExecute = func( 398 info trace.TableSessionQueryStreamExecuteStartInfo, 399 ) func( 400 trace.TableSessionQueryStreamExecuteIntermediateInfo, 401 ) func( 402 trace.TableSessionQueryStreamExecuteDoneInfo, 403 ) { 404 if d.Details()&trace.TableSessionQueryStreamEvents == 0 { 405 return nil 406 } 407 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "query", "stream", "execute") 408 session := info.Session 409 query := info.Query 410 l.Log(ctx, "start", 411 appendFieldByCondition(l.logQuery, 412 Stringer("query", info.Query), 413 String("id", session.ID()), 414 String("status", session.Status()), 415 )..., 416 ) 417 start := time.Now() 418 419 return func( 420 info trace.TableSessionQueryStreamExecuteIntermediateInfo, 421 ) func( 422 trace.TableSessionQueryStreamExecuteDoneInfo, 423 ) { 424 if info.Error == nil { 425 l.Log(ctx, "intermediate") 426 } else { 427 l.Log(WithLevel(ctx, WARN), "failed", 428 Error(info.Error), 429 versionField(), 430 ) 431 } 432 433 return func(info trace.TableSessionQueryStreamExecuteDoneInfo) { 434 if info.Error == nil { 435 l.Log(ctx, "done", 436 appendFieldByCondition(l.logQuery, 437 Stringer("query", query), 438 Error(info.Error), 439 String("id", session.ID()), 440 String("status", session.Status()), 441 latencyField(start), 442 )..., 443 ) 444 } else { 445 l.Log(WithLevel(ctx, ERROR), "failed", 446 appendFieldByCondition(l.logQuery, 447 Stringer("query", query), 448 Error(info.Error), 449 String("id", session.ID()), 450 String("status", session.Status()), 451 latencyField(start), 452 versionField(), 453 )..., 454 ) 455 } 456 } 457 } 458 } 459 t.OnSessionQueryStreamRead = func( 460 info trace.TableSessionQueryStreamReadStartInfo, 461 ) func( 462 intermediateInfo trace.TableSessionQueryStreamReadIntermediateInfo, 463 ) func( 464 trace.TableSessionQueryStreamReadDoneInfo, 465 ) { 466 if d.Details()&trace.TableSessionQueryStreamEvents == 0 { 467 return nil 468 } 469 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "query", "stream", "read") 470 session := info.Session 471 l.Log(ctx, "start", 472 String("id", session.ID()), 473 String("status", session.Status()), 474 ) 475 start := time.Now() 476 477 return func( 478 info trace.TableSessionQueryStreamReadIntermediateInfo, 479 ) func( 480 trace.TableSessionQueryStreamReadDoneInfo, 481 ) { 482 if info.Error == nil { 483 l.Log(ctx, "intermediate") 484 } else { 485 l.Log(WithLevel(ctx, WARN), "failed", 486 Error(info.Error), 487 versionField(), 488 ) 489 } 490 491 return func(info trace.TableSessionQueryStreamReadDoneInfo) { 492 if info.Error == nil { 493 l.Log(ctx, "done", 494 latencyField(start), 495 String("id", session.ID()), 496 String("status", session.Status()), 497 ) 498 } else { 499 l.Log(WithLevel(ctx, ERROR), "failed", 500 latencyField(start), 501 String("id", session.ID()), 502 String("status", session.Status()), 503 Error(info.Error), 504 versionField(), 505 ) 506 } 507 } 508 } 509 } 510 t.OnSessionTransactionBegin = func( 511 info trace.TableSessionTransactionBeginStartInfo, 512 ) func( 513 trace.TableSessionTransactionBeginDoneInfo, 514 ) { 515 if d.Details()&trace.TableSessionTransactionEvents == 0 { 516 return nil 517 } 518 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "tx", "begin") 519 session := info.Session 520 l.Log(ctx, "start", 521 String("id", session.ID()), 522 String("status", session.Status()), 523 ) 524 start := time.Now() 525 526 return func(info trace.TableSessionTransactionBeginDoneInfo) { 527 if info.Error == nil { 528 l.Log(ctx, "done", 529 latencyField(start), 530 String("id", session.ID()), 531 String("status", session.Status()), 532 String("tx", info.Tx.ID()), 533 ) 534 } else { 535 l.Log(WithLevel(ctx, WARN), "failed", 536 latencyField(start), 537 String("id", session.ID()), 538 String("status", session.Status()), 539 Error(info.Error), 540 versionField(), 541 ) 542 } 543 } 544 } 545 t.OnSessionTransactionCommit = func( 546 info trace.TableSessionTransactionCommitStartInfo, 547 ) func( 548 trace.TableSessionTransactionCommitDoneInfo, 549 ) { 550 if d.Details()&trace.TableSessionTransactionEvents == 0 { 551 return nil 552 } 553 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "tx", "commit") 554 session := info.Session 555 tx := info.Tx 556 l.Log(ctx, "start", 557 String("id", session.ID()), 558 String("status", session.Status()), 559 String("tx", info.Tx.ID()), 560 ) 561 start := time.Now() 562 563 return func(info trace.TableSessionTransactionCommitDoneInfo) { 564 if info.Error == nil { 565 l.Log(ctx, "done", 566 latencyField(start), 567 String("id", session.ID()), 568 String("status", session.Status()), 569 String("tx", tx.ID()), 570 ) 571 } else { 572 l.Log(WithLevel(ctx, ERROR), "failed", 573 latencyField(start), 574 String("id", session.ID()), 575 String("status", session.Status()), 576 String("tx", tx.ID()), 577 Error(info.Error), 578 versionField(), 579 ) 580 } 581 } 582 } 583 t.OnSessionTransactionRollback = func( 584 info trace.TableSessionTransactionRollbackStartInfo, 585 ) func( 586 trace.TableSessionTransactionRollbackDoneInfo, 587 ) { 588 if d.Details()&trace.TableSessionTransactionEvents == 0 { 589 return nil 590 } 591 ctx := with(*info.Context, TRACE, "ydb", "table", "session", "tx", "rollback") 592 session := info.Session 593 tx := info.Tx 594 l.Log(ctx, "start", 595 String("id", session.ID()), 596 String("status", session.Status()), 597 String("tx", tx.ID()), 598 ) 599 start := time.Now() 600 601 return func(info trace.TableSessionTransactionRollbackDoneInfo) { 602 if info.Error == nil { 603 l.Log(ctx, "done", 604 latencyField(start), 605 String("id", session.ID()), 606 String("status", session.Status()), 607 String("tx", tx.ID()), 608 ) 609 } else { 610 l.Log(WithLevel(ctx, ERROR), "failed", 611 latencyField(start), 612 String("id", session.ID()), 613 String("status", session.Status()), 614 String("tx", tx.ID()), 615 Error(info.Error), 616 versionField(), 617 ) 618 } 619 } 620 } 621 t.OnInit = func(info trace.TableInitStartInfo) func(trace.TableInitDoneInfo) { 622 if d.Details()&trace.TableEvents == 0 { 623 return nil 624 } 625 ctx := with(*info.Context, TRACE, "ydb", "table", "init") 626 l.Log(ctx, "start") 627 start := time.Now() 628 629 return func(info trace.TableInitDoneInfo) { 630 l.Log(WithLevel(ctx, INFO), "done", 631 latencyField(start), 632 Int("size_max", info.Limit), 633 ) 634 } 635 } 636 t.OnClose = func(info trace.TableCloseStartInfo) func(trace.TableCloseDoneInfo) { 637 if d.Details()&trace.TableEvents == 0 { 638 return nil 639 } 640 ctx := with(*info.Context, TRACE, "ydb", "table", "close") 641 l.Log(ctx, "start") 642 start := time.Now() 643 644 return func(info trace.TableCloseDoneInfo) { 645 if info.Error == nil { 646 l.Log(WithLevel(ctx, INFO), "done", 647 latencyField(start), 648 ) 649 } else { 650 l.Log(WithLevel(ctx, ERROR), "failed", 651 latencyField(start), 652 Error(info.Error), 653 versionField(), 654 ) 655 } 656 } 657 } 658 t.OnPoolStateChange = func(info trace.TablePoolStateChangeInfo) { 659 if d.Details()&trace.TablePoolLifeCycleEvents == 0 { 660 return 661 } 662 ctx := with(context.Background(), TRACE, "ydb", "table", "pool", "state", "change") 663 l.Log(WithLevel(ctx, DEBUG), "", 664 Int("size", info.Size), 665 String("event", info.Event), 666 ) 667 } 668 t.OnPoolSessionAdd = func(info trace.TablePoolSessionAddInfo) { 669 if d.Details()&trace.TablePoolLifeCycleEvents == 0 { 670 return 671 } 672 ctx := with(context.Background(), TRACE, "ydb", "table", "pool", "session", "add") 673 l.Log(ctx, "start", 674 String("id", info.Session.ID()), 675 String("status", info.Session.Status()), 676 ) 677 } 678 t.OnPoolSessionRemove = func(info trace.TablePoolSessionRemoveInfo) { 679 if d.Details()&trace.TablePoolLifeCycleEvents == 0 { 680 return 681 } 682 ctx := with(context.Background(), TRACE, "ydb", "table", "pool", "session", "remove") 683 l.Log(ctx, "start", 684 String("id", info.Session.ID()), 685 String("status", info.Session.Status()), 686 ) 687 } 688 t.OnPoolPut = func(info trace.TablePoolPutStartInfo) func(trace.TablePoolPutDoneInfo) { 689 if d.Details()&trace.TablePoolAPIEvents == 0 { 690 return nil 691 } 692 ctx := with(*info.Context, TRACE, "ydb", "table", "pool", "put") 693 session := info.Session 694 l.Log(ctx, "start", 695 String("id", session.ID()), 696 String("status", session.Status()), 697 ) 698 start := time.Now() 699 700 return func(info trace.TablePoolPutDoneInfo) { 701 if info.Error == nil { 702 l.Log(ctx, "done", 703 latencyField(start), 704 String("id", session.ID()), 705 String("status", session.Status()), 706 ) 707 } else { 708 l.Log(WithLevel(ctx, ERROR), "failed", 709 latencyField(start), 710 String("id", session.ID()), 711 String("status", session.Status()), 712 Error(info.Error), 713 versionField(), 714 ) 715 } 716 } 717 } 718 t.OnPoolGet = func(info trace.TablePoolGetStartInfo) func(trace.TablePoolGetDoneInfo) { 719 if d.Details()&trace.TablePoolAPIEvents == 0 { 720 return nil 721 } 722 ctx := with(*info.Context, TRACE, "ydb", "table", "pool", "get") 723 l.Log(ctx, "start") 724 start := time.Now() 725 726 return func(info trace.TablePoolGetDoneInfo) { 727 if info.Error == nil { 728 session := info.Session 729 l.Log(ctx, "done", 730 latencyField(start), 731 String("id", session.ID()), 732 String("status", session.Status()), 733 Int("attempts", info.Attempts), 734 ) 735 } else { 736 l.Log(WithLevel(ctx, WARN), "failed", 737 latencyField(start), 738 Int("attempts", info.Attempts), 739 Error(info.Error), 740 versionField(), 741 ) 742 } 743 } 744 } 745 t.OnPoolWait = func(info trace.TablePoolWaitStartInfo) func(trace.TablePoolWaitDoneInfo) { 746 if d.Details()&trace.TablePoolAPIEvents == 0 { 747 return nil 748 } 749 ctx := with(*info.Context, TRACE, "ydb", "table", "pool", "wait") 750 l.Log(ctx, "start") 751 start := time.Now() 752 753 return func(info trace.TablePoolWaitDoneInfo) { 754 fields := []Field{ 755 latencyField(start), 756 } 757 if info.Session != nil { 758 fields = append(fields, 759 String("id", info.Session.ID()), 760 String("status", info.Session.Status()), 761 ) 762 } 763 if info.Error == nil { 764 l.Log(ctx, "done", fields...) 765 } else { 766 fields = append(fields, Error(info.Error)) 767 l.Log(WithLevel(ctx, WARN), "failed", fields...) 768 } 769 } 770 } 771 772 return t 773 }