2017-10-07

Integration of PHP Error Handling


Integration of PHP Error Handling

Last week, on how to integrate PHP 5 error handling, I was asked to explain at multiple development projects.

As used herein, "Integration of PHP Error Handling" means, for example, the following development requirements.
# Normal Error
# Exception
# PHP Core Error
↓
We want to integrate and manage error handling such as logs and mails when these occur.
In order to realize the above, you need to understand the habit and mechanism of PHP error.
Here, we describe these realization methods.


Notice

# In this article, we will focus on these implementation methods.
# In this article, we do not mention general concepts such as "difference between error and exception".
# In this article, we describe it only for suspend processing.
# In this article, we do not mention restoration processing, trigger, processing level etc.
# This article applies only to PHP 5. It does not apply to PHP 7.


Flow

One way to realize this "PHP error integration processing" is the flow of error processing as follows.
Normal Error Processing  ←  Normal Error
↓
Exception Processing  ←  Exception
↓
Shutdown Processing  ←  PHP Core Error


Normal Error Handling

First, take handling authority of PHP Normal Error or User-Defined Error from PHP.
In PHP, the following functions are prepared.
mixed set_error_handler(callable $error_handler [, int $error_types = E_ALL | E_STRICT])
In order to take processing authority from PHP, create a callback function and register it in this function.
(Be sure to register it as early as possible of a series of request processing.)
Within the callback function, take the normal error and rethrow as an exception.
In short, the goal is to take the normal error handling authority from PHP, and pass it to the exception.
However, in this callback function, it is one point that PHP core error can not be taken.
public function handleError()
{
    //* Error Handler Definition
    function handleError($_number, $_message, $_file, $_line, $_contexts)
    {
         //* Not Includ Error Reporting
         if (! (error_reporting() & $_number)) {
             return;
         }
        //* to ErrorException
        throw new ErrorException($_message, 500, $_number, $_file, $_line);
    }

    //* Error Handler Set
    set_error_handler('handleError');
}


Exception Handling

Next, take exception processing authority which was not caught from PHP.
In PHP, the following functions are prepared.
callable set_exception_handler(callable $exception_handler)
In order to take processing authority from PHP, create a callback function and register it in this function.
(Be sure to register it as early as possible of a series of request processing.)
As a result, all normal errors and all uncaught exceptions are aggregated in one place.
But this is not enough.
We have not taken PHP Core Error yet.
Therefore, processing logic is not placed here.
public function handleException()
{
    //* Exception Handler Definition
    function handleException($_e)
    {
        //* Exception Context
         $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'] = $_e;

         //* Error Processing to Shutdown Logic
         exit;
    }

    //* Exception Handler Set
    set_exception_handler('handleException');
}


PHP Core Error Handling

In PHP 5, set_error_handler () can not take the processing authority of core error issued by PHP.
PHP 5 does not throw an exception of core error.
Therefore, in order to capture the PHP core error, the following function is used.
void register_shutdown_function(callable $callback [, mixed $parameter [, mixed $... ]])
This function makes it possible to register a callback function to be executed when script processing is completed or when exit () is called.
Utilizing this property, it is possible to integrate all processing such as error, exception, PHP core error, etc. as a result.
public function handleShutdown($_error_mails = array())
{
    //* Shutdown Function Definition
    function handleShutdown($_error_numbers = array(), $_error_mails = array(), $_http_status_codes = array())
    {
        //* Exception or Error
        if (! empty($_SERVER['X_EXCEPTION_HANDLER_CONTEXT'])) {
            $e = $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'];
            unset($_SERVER['X_EXCEPTION_HANDLER_CONTEXT']);
            $message = $e->__toString();
            $code = $e->getCode();
        } else {
            $e = error_get_last();
            //* Normal Exit
            if (empty($e)) {
                return;
            }

            //* Core Error
            $message = $_error_numbers[$e['type']] . ': ' . $e['message'] . ' in ' . $e['file'] . ' on line ' . $e['line'];
            $code = 500;
        }

        //* Error Logging
        error_log($message, 4);

        //* Error Mail
        $cmd = 'echo "' . $message . '" | mail -S "smtp=smtp://' . $_error_mails['host'] . '" -r "' . $_error_mails['from'] . '" -s "' . $_error_mails['subject'] . '" ' . $_error_mails['to'];
        $outputs = array();
        $status = null;
        $last_line = exec($cmd, $outputs, $status);

        //* HTTP Status Code
        header('HTTP/1.1 ' . $code . ' ' . $_http_status_codes[$code]);

        //* Shutdown
        exit($code . ' ' . $_http_status_codes[$code]);
    }

    //* Shutdown Function Registration
    $error_numbers = self::$error_numbers;
    $http_status_codes = self::$http_status_codes;
    register_shutdown_function('handleShutdown', $error_numbers, $_error_mails, $http_status_codes);
}


to Class Library

When these are made into a general purpose class library, it becomes as follows.
Logging, e-mail, exception context delivery, etc. should be changed according to circumstances.
class AppE
{

    public static $error_numbers = array(
        1 => 'Fatal',
        2 => 'Warning',
        4 => 'Parse Error',
        8 => 'Notice',
        16 => 'Core Fatal',
        32 => 'Core Warning',
        64 => 'Compile Error',
        128 => 'Compile Warning',
        256 => 'Ex Error',
        512 => 'Ex Warning',
        1024 => 'Ex Notice',
        2048 => 'Strict Error',
        4096 => 'Recoverable Error',
        8192 => 'Deprecated',
        16384 => 'Ex Deprecated',
        32767 => 'All',
    );

    //* HTTP Status Code
    public static $http_status_codes = array(
        'default' => 200,
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status',
        226 => 'IM Used',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
       404 => 'Not Found',
       405 => 'Method Not Allowed',
       406 => 'Not Acceptable',
       407 => 'Proxy Authentication Required',
       408 => 'Request Timeout',
       409 => 'Conflict',
       410 => 'Gone',
       411 => 'Length Required',
       412 => 'Precondition Failed',
       413 => 'Request Entity Too Large',
       414 => 'Request-URI Too Long',
       415 => 'Unsupported Media Type',
       416 => 'Requested Range Not Satisfiable',
       417 => 'Expectation Failed',
       418 => "I'm a teapot",
       422 => 'Unprocessable Entity',
       423 => 'Locked',
       424 => 'Failed Dependency',
       426 => 'Upgrade Required',
       500 => 'Internal Server Error',
       501 => 'Not Implemented',
       502 => 'Bad Gateway',
       503 => 'Service Unavailable',
       504 => 'Gateway Timeout',
       505 => 'HTTP Version Not Supported',
       506 => 'Variant Also Negotiates',
       507 => 'Insufficient Storage',
       509 => 'Bandwidth Limit Exceeded',
       510 => 'Not Extended',
    );


    public function __construct()
    {}


    public function handleError()
    {
        //* Error Handler Definition
        function handleError($_number, $_message, $_file, $_line, $_contexts)
        {
             //* Not Includ Error Reporting
             if (! (error_reporting() & $_number)) {
                 return;
             }
            //* to ErrorException
            throw new ErrorException($_message, 500, $_number, $_file, $_line);
        }

        //* Error Handler Set
        set_error_handler('handleError');
    }


    public function handleException()
    {
        //* Exception Handler Definition
        function handleException($_e)
        {
            //* Exception Context
             $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'] = $_e;

             //* Error Processing to Shutdown Logic
             exit;
        }

        //* Exception Handler Set
        set_exception_handler('handleException');
    }


    public function handleShutdown($_error_mails = array())
    {
        //* Shutdown Function Definition
        function handleShutdown($_error_numbers = array(), $_error_mails = array(), $_http_status_codes = array())
        {
            //* Exception or Error
            if (! empty($_SERVER['X_EXCEPTION_HANDLER_CONTEXT'])) {
                $e = $_SERVER['X_EXCEPTION_HANDLER_CONTEXT'];
                unset($_SERVER['X_EXCEPTION_HANDLER_CONTEXT']);
                $message = $e->__toString();
                $code = $e->getCode();
            } else {
                $e = error_get_last();
                //* Normal Exit
                if (empty($e)) {
                    return;
                }

                //* Core Error
                $message = $_error_numbers[$e['type']] . ': ' . $e['message'] . ' in ' . $e['file'] . ' on line ' . $e['line'];
                $code = 500;
            }

            //* Error Logging
            error_log($message, 4);

            //* Error Mail
            $cmd = 'echo "' . $message . '" | mail -S "smtp=smtp://' . $_error_mails['host'] . '" -r "' . $_error_mails['from'] . '" -s "' . $_error_mails['subject'] . '" ' . $_error_mails['to'];
            $outputs = array();
            $status = null;
            $last_line = exec($cmd, $outputs, $status);

            //* HTTP Status Code
            header('HTTP/1.1 ' . $code . ' ' . $_http_status_codes[$code]);

            //* Shutdown
            exit($code . ' ' . $_http_status_codes[$code]);
        }

        //* Shutdown Function Registration
        $error_numbers = self::$error_numbers;
        $http_status_codes = self::$http_status_codes;
        register_shutdown_function('handleShutdown', $error_numbers, $_error_mails, $http_status_codes);
    }

}


Afterword

Many PHP frameworks provide interfaces for extended error handlers and extended exception handlers, but their contents merely implement similar things.
Recently, when the story of this layer came up, the number of engineers who understood these technologies has become really few.
This is what has been said from around the time the non-Java web application framework came out.
I realize that it has become a reality at many development sites.
A lot of recent engineers use the framework, that is not a means, it has become a purpose.
It can be seen from the fact that the number of engineers who do not know the basis of Web application development has increased.
In this way, the factor that the number of tool engineers has increased.
One of them is the increase in speed development method like Silicon Valley.
It is by no means negative.
In short, if we categorize the category of development engineers more subdivided, the misrecognition of mutual recognition will probably decrease.

2017-06-25

Distributed Model Semantics

I sometimes receive questions from engineers at lectures on Distributed and Cooperative Autonomous Network Systems, ML, working groups and others.

"Chief, I think that model is not a Distributed Model, but a Replicated Model."

Many engineers misunderstand how to catch this semantics.
In the case of this semantics, the Distributed Model is as follows.

∀x ( x ∈ Replicated → x ∈ Deistributed ) Replicated ⊆ Distributed ∀x ( x ∈ Segmented → x ∈ Deistributed ) Segmented ⊆ Distributed Distributed = { Replicated, Segmented, ... } Replicated != Segmented

Why does such a misunderstanding happen?
What can be considered as the cause is ambiguous explanation of RAID level or ambiguous naming of distributed system middleware.

For example, although not strictly a mistake,

ex.) Striping of raid 0 is Distributed Model.
ex.) Mirroring of raid 1 is Replicated Model.

As it explains, many engineers misunderstand.
Both RAID 0 and RAID 1 are Distributed Model.

The RAID 0 Model distributes the data after spliting the data.
The RAID 1 Model distributes the data after replicating the data.

If I write so far, you will understand anymore.
Both are Distributed Model.

Of course, it is a Distributed Model because it distributes data.
If you do not distribute and arrange the data, it is not a Distributed Model.

This semantics also does not depend on distributing units.
Whether it is a bit unit, a block unit, a chunk unit, a record unit, a table unit, a file unit, a data resource unit, a node unit, or a cluster It might be a unit.

These are all Distributed Models for the purpose such as HA.

Hot Standby HA Architecture Pattern on AWS EC2


Hot Standby HA of No E/ALB by Unicast VRRP

This Hot Standby HA Architecture Pattern realizes VRRP monitor by Unicast in the AWS network that Multicast can not use.
In particular, this design is a useful HA architecture pattern in staging environments of small projects and so on which costs such as E/ALB SaaS need not be paid.


EC2 + RHEL7 + Unicast VRRP + Failure Scripts

  • IaaS: AWS EC2
  • OS: RHEL 7 or CentOS 7
  • Unicast VRRP: keepalived
  • Failover & Failback Scripts: Bash + AWS CLI


keepalived Install & Configuration

$ : Node 1
$ sudo yum -y install keepalived
$ sudo cp -a /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.org
$ sudo vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
    notification_email {
        admin@example.com
    }
    notification_email_from node1@example.com
    smtp_server mail.example.com
    smtp_connect_timeout 30
    router_id node1.example.com
}

vrrp_instance VI_0 {
    state MASTER
    interface eth0
    virtual_router_id 10
    priority 101
    nopreempt
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass foo
    }

    ! VIP
    virtual_ipaddress {
        10.10.10.10 dev eth0
    }

    ! Node 1
    unicast_src_ip 10.10.10.11

    ! Node2
    unicast_peer {
        10.10.10.12
    }
}
$ : Node 2
$ sudo yum -y install keepalived
$ sudo cp -a /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.org
$ sudo vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
    notification_email {
        admin@example.com
    }
    notification_email_from node2@example.com
    smtp_server mail.example.com
    smtp_connect_timeout 30
    router_id node2.example.com
}

vrrp_instance VI_0 {
    state BACKUP
    interface eth0
    virtual_router_id 10
    priority 100
    nopreempt
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass foo
    }

    ! VIP
    virtual_ipaddress {
        10.10.10.10 dev eth0
    }

    ! Node 2
    unicast_src_ip 10.10.10.12

    ! Node 1
    unicast_peer {
        10.10.10.11
    }

    ! Failover Script
    notify_master "/etc/keepalived/failover.sh"
}

Failback Script (Bash + AWS CLI)

$ : Node 1
$ : for manual failback
$ sudo touch /etc/keepalived/failback.sh
$ sudo vim /etc/keepalived/failback.sh
#!/bin/bash
# failback.sh

# LAN VIP
VIP=10.10.10.10

# WAN VIP
ALLOCATION_ID=eipalloc-xxxxxxx0

# Instance 1 eth0 IF
INTERFACE_ID_1=eni-xxxxxxx1

# Instance 2 eth0 IF
INTERFACE_ID_2=eni-xxxxxxx2

# Instance ID
INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`

# Auth
export AWS_DEFAULT_REGION=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | rev | cut -c 2- | rev`

# LAN VIP Unassitnment
aws ec2 unassign-private-ip-addresses --private-ip-addresses $VIP --network-interface-id $INTERFACE_ID_2

# LAN VIP Assignment
aws ec2 assign-private-ip-addresses --private-ip-addresses $VIP --network-interface-id $INTERFACE_ID_1 --allow-reassignment

# WAN VIP Asoociation
aws ec2 associate-address --allocation-id $ALLOCATION_ID --network-interface-id $INTERFACE_ID_1 --private-ip-address $VIP

Failover Script (Bash + AWS CLI)

$ : Node 2
$ : for auto failover
$ sudo touch /etc/keepalived/faiover.sh
$ sudo vim /etc/keepalived/faiover.sh
#!/bin/bash
# failover.sh

# LAN VIP
VIP=10.10.10.10

# WAN VIP
ALLOCATION_ID=eipalloc-xxxxxxx0

# Instance 1 eth0 IF
INTERFACE_ID_1=eni-xxxxxxx1

# Instance 2 eth0 IF
INTERFACE_ID_2=eni-xxxxxxx2

# Instance ID
INSTANCE_ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`

# Auth
export AWS_DEFAULT_REGION=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | rev | cut -c 2- | rev`

# LAN VIP Unassitnment
aws ec2 unassign-private-ip-addresses --private-ip-addresses $VIP --network-interface-id $INTERFACE_ID_1

# LAN VIP Assignment
aws ec2 assign-private-ip-addresses --private-ip-addresses $VIP --network-interface-id $INTERFACE_ID_2 --allow-reassignment

# WAN VIP Asoociation
aws ec2 associate-address --allocation-id $ALLOCATION_ID --network-interface-id $INTERFACE_ID_2 --private-ip-address $VIP

keepalived Daemon Start

$ : Node 1
$ sudo systemctl start keepalived
$ sudo systemctl enable keepalived
$ sudo systemctl status keepalived
$ sudo ip addr
$ : Node 2
$ sudo systemctl start keepalived
$ sudo systemctl enable keepalived
$ sudo systemctl status keepalived
$ sudo ip addr

Auto Failover Test

$ : Node 1
$ sudo systemctl stop keepalived
$ sudo systemctl status keepalived
$ sudo ip addr
$ : Node 2
$ sudo ip addr

Manual Failback Test

$ : Node 1
$ sudo systemctl start keepalived
$ sudo systemctl status keepalived
$ : Node 2
$ sudo /etc/keepalived/failback.sh
$ sudo ip addr
$ : Node 1
$ sudo ip addr