Spring Boot Advanced Debugging Sinhala Guide | Remote Debugging & Tips

Spring Boot Advanced Debugging Sinhala Guide | Remote Debugging & Tips

ආයුබෝවන් යාලුවනේ! Spring Boot Advanced Debugging Sinhala Guide

Software development කියන්නේ ගොඩක් වෙලාවට code ලියනවා වගේම, code එකේ තියෙන bugs හොයන එක. සමහර වෙලාවට bugs හොයන එක අළුතින් feature එකක් develop කරනවාට වඩා අමාරුයි, කාලය යනවා නේද? විශේෂයෙන්ම Spring Boot වගේ complex applications වලදී පොඩි වැරැද්දක් හොයාගන්න පැය ගණන් යන්න පුළුවන්. ඒත් ඔයාට හොඳ debugging skills තියෙනවා නම් මේ වැඩේ ගොඩක් ලේසි කරගන්න පුළුවන්.

අද අපි මේ guide එකෙන් කතා කරන්නේ Spring Boot applications වල debugging techniques ගැන. සාමාන්‍යයෙන් IDE එකෙන් කරන local debugging වලට අමතරව, remote debugging, conditional breakpoints වගේ advanced ක්‍රම ගැනත් අපි විස්තරාත්මකව බලමු. මේ guide එක අවසානෙදි ඔයාට පුළුවන් වෙයි complex Spring Boot issues වුණත් confidence එකෙන් debug කරන්න! එහෙනම්, අපි පටන් ගමු!

1. Debugging කියන්නේ මොකක්ද? (What is Debugging?)

Debugging කියන්නේ software එකක තියෙන errors (bugs) හොයාගෙන, ඒවා නිවැරදි කරන ක්‍රියාවලිය. සරලව කිව්වොත්, ඔයාගේ program එක run වෙනකොට ඒක ඇතුළට ගිහින් බලනවා වගේ දෙයක් තමයි මේක. මේකෙන් වෙන්නේ program එකේ flow එක කොහොමද සිද්ධ වෙන්නේ, variables වල values මොනවද, method calls සිද්ධ වෙන්නේ කොහොමද වගේ දේවල් step-by-step විමර්ශනය කරන්න පුළුවන් වීම.

ඇයි Debugging වැදගත් වෙන්නේ?

  • Errors ඉක්මනින් හොයාගන්න: System එකේ තියෙන errors මොනවද කියලා ඉක්මනින්ම identify කරන්න පුළුවන්.
  • Code එක තේරුම් ගන්න: අළුත් code එකක්, නැත්නම් වෙන කෙනෙක් ලියපු code එකක් understand කරගන්න debugging එක ගොඩක් උදව් වෙනවා.
  • Performance issues: සමහර වෙලාවට application එක slow වෙන්නේ ඇයි කියලා හොයාගන්න debugging techniques උදව් වෙනවා.
  • Confidence: හොඳ debugging skills තියෙනවා කියන්නේ, ඔයාට ඕනෑම situation එකකදී bug එකක් හොයාගෙන solve කරන්න පුළුවන් කියලා confidence එකක් ලැබෙනවා.

ප්‍රධාන Debugging Concepts:

  • Breakpoint: Program එක run වෙනකොට, යම්කිසි line එකකදී pause කරන්න දාන සලකුණක්. Debugger එක ඒ line එකට ආවම program එක නවතිනවා.
  • Stepping: Program එක නවත්තලා තියෙන තැන ඉඳලා line-by-line execute කරන එක.
    • Step Over (F8): වත්මන් line එක execute කරලා ඊළඟ line එකට යනවා. Method call එකක් නම්, ඒ method එක ඇතුලට නොගිහින් whole method call එක execute කරනවා.
    • Step Into (F7): වත්මන් line එක method call එකක් නම්, ඒ method එක ඇතුලට ගිහින්, ඒ method එකේ පළවෙනි line එකට යනවා.
    • Step Out (Shift+F8): වත්මන් method එකෙන් එළියට ඇවිත්, ඒ method එක call කරපු line එකෙන් පස්සේ තියෙන line එකට යනවා.
  • Resume (F9): Program එක නවත්තලා තියෙන තැන ඉඳලා ඊළඟ breakpoint එකට යනකල්, නැත්නම් program එක ඉවර වෙනකල් execute කරනවා.
  • Variables Window: Breakpoint එකක් ලඟ program එක නවත්තපු වෙලාවේ, ඒ scope එකේ තියෙන සියළුම variables වල current values බලන්න පුළුවන්.
  • Evaluate Expression: Run time එකේදී, ඕනෑම expression එකක value එක බලන්න පුළුවන්. (e.g., myObject.getName() + " " + myOtherObject.getId())

2. Local Debugging Spring Boot Applications

Spring Boot application එකක් local computer එකේ run කරනකොට debug කරන එක තමයි අපි හැමෝම මුලින්ම ඉගෙන ගන්නේ. මේකට අපි IntelliJ IDEA වගේ IDE එකක් පාවිච්චි කරමු.

පියවර 1: Spring Boot Project එකක් හදාගමු

මුලින්ම අපි සරල Spring Boot web application එකක් හදාගමු. Spring Initializr එකට ගිහින්, Web dependency එක add කරලා project එක generate කරගන්න.

පහත කෝඩ් එකෙන් සරල REST Controller එකක් හදාගමු:

package com.example.demodebugging;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/greet")
    public String greet(@RequestParam String name) {
        String message = "Hello, " + name + "!";
        // Let's introduce a potential bug or a point of interest
        if (name.length() > 10) {
            message = "That's a long name: " + name; // This line might be interesting to debug
        }
        return message;
    }

    @GetMapping("/calculate")
    public int calculate(@RequestParam int num1, @RequestParam int num2) {
        int sum = num1 + num2;
        int product = num1 * num2; // This is a good place to set a breakpoint
        int division = sum / product; // What if product is 0? Or division by zero?
        return division;
    }
}

පියවර 2: Debug Configuration එක හදමු

IntelliJ IDEA එකේදී, Run menu එකට ගිහින් Edit Configurations... ක්ලික් කරන්න. සාමාන්‍යයෙන් Spring Boot project එකක් import කරාම auto-generate වෙන Spring Boot configuration එකක් තියෙනවා. ඒක select කරලා, Debug button එක ඔබන්න.

පියවර 3: Breakpoints දාලා Debug කරමු

අපි DemoController එකේ calculate method එකේ int product = num1 * num2; කියන line එකේ breakpoint එකක් දාමු. ඒකට line number එකට එහායින් ක්ලික් කරන්න. රතු පාට තිතක් එයි.

දැන්, Debug icon එක ඔබලා application එක debug mode එකෙන් run කරන්න. Browser එකේදී මේ URL එකට යන්න: http://localhost:8080/calculate?num1=5&num2=10

දැන් මොකද වෙන්නේ? IntelliJ IDEA එකේ debugger එක activate වෙලා, ඔයා දාපු breakpoint එක ලඟ program එක නවතිනවා. එතකොට ඔයාට පුළුවන්:

  • Variables window එකේ num1, num2, sum වගේ variables වල values බලන්න.
  • Step Over (F8) කරලා ඊළඟ line එකට යන්න.
  • Evaluate Expression (Alt+F8) කරලා num1 * 2 වගේ expression එකක් run කරලා බලන්න.

උදාහරණයක් විදියට, num1=5, num2=10 දීලා product variable එකේ value එක 50 කියලා Variables window එකේ පේනවා ඇති. division line එකට ගිහින්, sum එක 15, product එක 50 නිසා, division එක 0 බව (integer division නිසා) බලන්න පුළුවන්.

3. Advanced Debugging Techniques

සාමාන්‍ය debugging වලට අමතරව, තවත් ක්‍රම කිහිපයක් තියෙනවා complex scenarios වලදී debugging පහසු කරගන්න.

3.1 Conditional Breakpoints

සමහර වෙලාවට අපිට ඕන වෙන්නේ breakpoint එකක් නවතින්න යම්කිසි condition එකක් fulfill වුණාම විතරයි. උදාහරණයක් විදියට, name parameter එකේ length එක 10ට වඩා වැඩි වුණොත් විතරක් breakpoint එක නතර කරන්න ඕනේ නම්.

  • Breakpoint එකක් දාලා, ඒක උඩ right-click කරන්න.
  • එතකොට එන window එකේ Condition කියන box එකේ, ඔයාට ඕන condition එක ලියන්න.
    • උදාහරණයක්: name.length() > 10
  • Suspend checkbox එක All (default) විදියට තියන්න.

දැන් http://localhost:8080/greet?name=Nimal කියලා call කරොත් breakpoint එක නවතින්නේ නැහැ. ඒත් http://localhost:8080/greet?name=ThisIsAVeryLongName කියලා call කරොත්, if (name.length() > 10) කියන line එකේ breakpoint එක නතර වෙනවා. මේක loop එකක් ඇතුලේ වගේ දේවල් වලදී ගොඩක් ප්‍රයෝජනවත්.

3.2 Logging vs. Debugging: Best Practices

ගොඩක් developers ලා bug එකක් ආවම System.out.println() නැත්නම් log.info() දාලා තමයි බලන්නේ. මේක හොඳ ක්‍රමයක් වුණත්, debugging environment එකකදී debugger එක පාවිච්චි කරන එක වඩාත් efficient.

  • Debugger: Run time එකේදී code එකේ ඕනෑම තැනක නවත්තලා variables, call stack එක වගේ දේවල් බලන්න පුළුවන්. Real-time problem solving වලට හොඳයි.
  • Logging: Production environment එකකදී ගැටළු trace කරන්න, application එකේ ක්‍රියාකාරිත්වය monitor කරන්න logging අත්‍යවශ්‍යයි. Debugging purpose එකට දාන logs, production එකට යන්න කලින් අයින් කරන්න ඕනේ, නැත්නම් performance issue වෙන්න පුළුවන්.

හොඳම දේ තමයි, development phase එකේදී debugging tools පාවිච්චි කරලා ඉක්මනින් issues solve කරලා, production environment එකට ප්‍රයෝජනවත් වෙන විදියට proper logging implementation එකක් තියාගන්න එක.

3.3 Watch Expressions

Variables window එකේ පේන්නේ current scope එකේ තියෙන variables විතරයි. සමහර වෙලාවට අපිට ඕන වෙනවා complex object එකක nested field එකක value එක, නැත්නම් method call එකක result එක නිතරම watch කරන්න. මේකට Watch expressions පාවිච්චි කරන්න පුළුවන්.

Debugger එක active වෙලා තියෙනකොට Watches tab එකට ගිහින් + button එක ක්ලික් කරලා, ඔයාට watch කරන්න ඕන expression එක enter කරන්න. (e.g., user.getAddress().getCity(), myList.size())

3.4 Exception Breakpoints

සමහර වෙලාවට අපිට හරියටම දන්නේ නැහැ exception එකක් එන්නේ කොතනින්ද කියලා. මේ වගේ වෙලාවට Exception Breakpoint එකක් දාන්න පුළුවන්. ඕනෑම Exception එකක් throw වුණාම debugger එක auto-matically නවතිනවා.

  • IntelliJ IDEA එකේ Breakpoints window එකට යන්න (Run -> View Breakpoints...).
  • + icon එක ක්ලික් කරලා Java Exception Breakpoints select කරන්න.
  • Any Exception කියන එක tick කරන්න, නැත්නම් ඔයාට ඕන specific exception class එක (e.g., NullPointerException) type කරන්න.

මේක ගොඩක්ම ප්‍රයෝජනවත්, ඔයාගේ application එක RuntimeException එකක් දෙනවා, ඒත් stack trace එකෙන් හරියටම cause එක හොයාගන්න බැරි වෙලාවට.

4. Remote Debugging Spring Boot Applications

Local debugging කියන්නේ development environment එකට හොඳයි. ඒත් ඔයාගේ application එක වෙන server එකක, staging environment එකක, නැත්නම් production environment එකක run වෙනකොට bug එකක් ආවොත් කොහොමද? ඒ වෙලාවට තමයි remote debugging අවශ්‍ය වෙන්නේ. Remote debugging කියන්නේ, ඔයාගේ IDE එකෙන් remote server එකක run වෙන process එකක් debug කරන එක.

Remote Debugging ක්‍රියාකරන ආකාරය:

  1. Target Application Configuration: Remote server එකේ run වෙන Spring Boot application එක start කරන්න ඕනේ special JVM arguments දීලා. මේ arguments වලින් කියන්නේ, debugger එක connect වෙන්න ඕන port එක සහ mode එක (server/client).
  2. IDE Connection: ඔයාගේ IDE එක (IntelliJ IDEA) ඒ port එකට connect වෙලා, debugging session එකක් establish කරනවා.
  3. Debugging: ඊට පස්සේ ඔයාට පුළුවන් local debugging කරනවා වගේම breakpoints දාගෙන, variables check කරගෙන debug කරන්න.

පියවර 1: Spring Boot Application එක Remote Debugging සඳහා Configure කිරීම

ඔයාගේ Spring Boot JAR එක server එකේ run කරනකොට, පහත JVM arguments add කරන්න:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar your-spring-boot-app.jar

මේ arguments ගැන ටිකක් විස්තර කරමු:

  • -agentlib:jdwp: Java Debug Wire Protocol (JDWP) agent එක load කරන්න කියලා කියනවා. මේක තමයි debugger එකට communication channel එක සපයන්නේ.
  • transport=dt_socket: Socket transport layer එක පාවිච්චි කරන්න කියලා කියනවා.
  • server=y: Debugger එක server mode එකෙන් run කරන්න කියලා කියනවා. (IDE එක client විදියට connect වෙනවා)
  • suspend=n: JVM එක debugger එක connect වෙනකල් suspend (pause) කරන්න එපා කියලා කියනවා. suspend=y කියලා දැම්මොත් application එක start වෙන්නේ නැහැ, debugger එක connect වෙනකල් බලන් ඉන්නවා. Production වලදී n දාන එක තමයි හොඳ.
  • address=5005: Debugger එක connect වෙන්න ඕන port number එක. මේක network port එකක් නිසා, firewall එකේ මේ port එක open වෙලා තියෙන්න ඕනේ.

Docker Container එකක් ඇතුලේ නම්:

Docker file එකේ ENTRYPOINT නැත්නම් CMD එකට මේ arguments add කරන්න පුළුවන්:

FROM openjdk:17-jdk-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
EXPOSE 8080 5005

මෙහිදී address=*:5005 යොදාගන්නේ container එකේ ඕනෑම network interface එකකින් connection ගන්න.

පියවර 2: IntelliJ IDEA එකෙන් Remote Debugger එකට Connect වීම

  1. IntelliJ IDEA එකේ Run menu එකට ගිහින් Edit Configurations... ක්ලික් කරන්න.
  2. + icon එක ඔබලා Remote JVM Debug තෝරන්න.
  3. Configuration එකට නමක් දෙන්න (e.g., "My Remote Spring Boot App").
  4. Host: එකට remote server එකේ IP address එක නැත්නම් hostname එක දෙන්න.
  5. Port: එකට 5005 (නැත්නම් ඔයා configure කරපු port එක) දෙන්න.
  6. Command line arguments for remote JVM: කියන තැන, IDE එකෙන් generate කරලා දෙනවා. ඒක හරියටම අපේ server එකේ run කරන command එකට ගැලපෙනවද කියලා බලන්න.
  7. Apply කරලා OK කරන්න.

දැන්, ඔයාගේ IDE එකේ Debug icon එක ඔබලා මේ remote configuration එක run කරන්න. IDE එක remote server එකට connect වෙයි. Connection එක successful වුණාම, ඔයාට පුළුවන් local debugging කරනවා වගේම remote server එකේ run වෙන application එක debug කරන්න.

වැදගත් කරුණු:

  • Network Access: Remote server එකේ 5005 port එකට (හෝ භාවිතා කරන port එකට) ඔයාගේ local machine එකෙන් access තියෙන්න ඕනේ. Firewall rules මේකට බලපාන්න පුළුවන්.
  • Source Code Matching: Local IDE එකේ තියෙන source code එකයි, remote server එකේ deploy කරලා තියෙන source code එකයි එකම version එකක් වෙන්න ඕනේ. නැත්නම් breakpoints වැරදි තැන්වල නවතින්න පුළුවන්, නැත්නම් No executable code at breakpoint වගේ errors එන්න පුළුවන්.
  • Performance Impact: Debugging agent එක load කිරීමෙන් සහ active debugging session එකක් නිසා application එකේ performance එකට පොඩි බලපෑමක් වෙන්න පුළුවන්. ඒ නිසා production environment වලදී, අවශ්‍යම වුණොත් විතරක් remote debugging enable කරන්න.

5. Practical Exercise: Debugging a Complex Scenario

අපි පොඩ්ඩක් complex scenario එකක් හිතමු. ඔයාගේ application එක user login එකක් handle කරනවා. ඒකේ role-based access control එකක් තියෙනවා. සමහර users ලට admin access දෙන්නේ නැහැ කියලා complaint එකක් එනවා.

අපි හිතමු මේ වගේ UserService එකක් තියෙනවා කියලා:

package com.example.demodebugging;

import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class UserService {

    private final Map<String, User> users = new HashMap<>();

    public UserService() {
        users.put("admin", new User("admin", "[email protected]", true));
        users.put("user1", new User("user1", "[email protected]", false));
        users.put("user2", new User("user2", "[email protected]", false));
    }

    public User findByUsername(String username) {
        return users.get(username);
    }

    public boolean isAdmin(String username) {
        User user = findByUsername(username);
        // Potential bug: What if user is null? Or isAdmin field is incorrect?
        return user != null && user.isAdmin();
    }
}

class User {
    private String username;
    private String email;
    private boolean admin;

    public User(String username, String email, boolean admin) {
        this.username = username;
        this.email = email;
        this.admin = admin;
    }

    // Getters
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public boolean isAdmin() { return admin; }

    // Setters (if needed, but for simplicity, not included here)
}

මේක call කරන controller එකක්:

package com.example.demodebugging;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AdminController {

    private final UserService userService;

    public AdminController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/checkAdmin")
    public String checkAdmin(@RequestParam String username) {
        boolean isAdmin = userService.isAdmin(username);
        if (isAdmin) {
            return "User " + username + " has admin access.";
        } else {
            return "User " + username + " does NOT have admin access.";
        }
    }
}

Bug Scenario:

user1 කියන user ට admin access නැති වුණත්, /checkAdmin?username=user1 කියලා call කරාම "User user1 has admin access." කියලා එනවා. (Assume this is a bug, even though our code looks correct, let's pretend a subtle bug exists or we need to verify the logic.)

Debugging Steps:

  1. UserService එකේ isAdmin method එකේ return user != null && user.isAdmin(); කියන line එකේ breakpoint එකක් දාන්න.
  2. Application එක Debug mode එකෙන් run කරන්න.
  3. Browser එකේ http://localhost:8080/checkAdmin?username=user1 URL එකට යන්න.
  4. Debugger එක breakpoint එක ලඟ නවතිනවා.
  5. Variables window එක බලන්න. username variable එකේ "user1" කියලා තියෙනවා. user object එකේ "user1" ගේ details තියෙනවා, ඒ වගේම admin field එක false කියලා පේනවා.
  6. දැන් Step Over (F8) කරලා return user != null && user.isAdmin(); කියන expression එකේ result එක බලන්න.
    • user != null කියන එක true වෙයි.
    • user.isAdmin() කියන එක false වෙයි.
    • true && false කියන්නේ false.
    • isAdmin variable එකට false assign වෙනවා.
  7. AdminController එකේ if (isAdmin) condition එකට ගියාම, isAdmin false නිසා, else block එක execute වෙලා "User user1 does NOT have admin access." කියලා return කරනවා.

Wait! ඉහත scenario එකේදී, අපි code එකේ තියෙන විදියටම debug කරාම, bug එකක් නැහැ කියලා පේනවා. ඒක හොඳ දෙයක්! සමහර වෙලාවට අපිට user ගේ input එකත් එක්ක code එක ක්‍රියාත්මක වෙන විදිය verify කරන්න ඕනේ.

Real Bug Example:

අපි හිතමු UserService එකේ මෙහෙම වැරැද්දක් තියෙනවා කියලා:

// Inside UserService
public boolean isAdmin(String username) {
    User user = findByUsername(username);
    // Real bug: accidentally returning true for specific username
    if ("user1".equals(username)) { // This is a mistake!
        return true;
    }
    return user != null && user.isAdmin();
}

දැන් user1 කියන user ට admin access දෙන්නේ නැහැ කියලා complaint එකක් ආවොත්, isAdmin method එකේ breakpoint එකක් දාලා, user1 username එක දීලා call කරාම, debugger එක if ("user1".equals(username)) කියන line එකේදී true return කරනවා කියලා පෙන්වයි. මේකෙන් ඔයාට ඉක්මනින්ම bug එක හොයාගන්න පුළුවන්.

මේ වගේ complex situations වලදී, conditional breakpoints, watch expressions වගේ advanced features පාවිච්චි කරලා, ඔයාට ඕන specific condition එකක් satisfy වෙන වෙලාවට විතරක් program එක නවත්තලා, අවශ්‍ය variables analyze කරලා, ඉක්මනින්ම root cause එක හොයාගන්න පුළුවන්.

Conclusion:

ඉතින් යාලුවනේ, මේ guide එකෙන් ඔයාලට Spring Boot applications debug කරන හැටි ගැන හොඳ අවබෝධයක් ලැබෙන්න ඇති කියලා හිතනවා. Debugging කියන්නේ code ලියනවා වගේම, software development process එකේ අත්‍යවශ්‍ය කොටසක්. හොඳ debugging skills තියෙනවා කියන්නේ, ඔයාට bugs ඉක්මනින් solve කරන්න පුළුවන් වගේම, complex systems වල code flow එක හරියට තේරුම් ගන්නත් පුළුවන්.

අපි අද local debugging, advanced techniques වගේම remote debugging ගැනත් කතා කරා. මේ හැම technique එකක්ම ඔයාගේ developer tool-kit එකට එකතු කරගන්න. ඊලඟ වතාවේ bug එකක් ආවම කලබල නොවී, debugger එක open කරලා, step-by-step issue එක solve කරන්න උත්සාහ කරන්න.

ඔයාගේ අත්දැකීම් කොහොමද? ඔයා පාවිච්චි කරන favorite debugging tip එකක් තියෙනවා නම් comment section එකේ අපිටත් කියන්න! මේ knowledge එක ඔයාගේ next project එකේදී පාවිච්චි කරන්න අමතක කරන්න එපා. ජය වේවා!